美文网首页
Java梳理之理解IO(一)

Java梳理之理解IO(一)

作者: _小二_ | 来源:发表于2017-10-14 17:37 被阅读0次

对于IO部分,写的并不多,通常情况下都是复用现有的代码,致使很多细节都理解的并不透彻,现在梳理一下字节字符流。

前置:

File类

过去对这个类理解的并不透彻,一直把File类当成一个文件来看,却没有看到,它其实是用来处理文件和文件系统,我们可以用它来处理文件目录,也可以用它处理文件,集两大功能于一身。例如:

/**
**file示例
**/
public class FileDemo {
    public static void main(String[] arg0) throws IOException{
        File dir = new File("/test");
        if(!dir.exists()){
            dir.mkdirs();
        }
        System.out.println(dir.isDirectory());
        File file = new File(dir,"test1.txt");
        if(!file.exists()){
            file.createNewFile();
        }
        System.out.println(file.isFile());
        File[] files = dir.listFiles();
        for(File f:files){
            System.out.println(f.getAbsolutePath());
        }
    }
}
输出:
true
true
F:\test\test1.txt

如上所示,对于创建的File类,有一个判断exists()方法,如果该File存在,则创建。因为File类不单单是指代文件类型,所以创建的时候需要明确自己需要的是目录还是文件,对于目录来说,有两个方法,如上的mkdirs()方法,它会将文件目录的所有结构都创建出来,不管父目录之前存不存在,而另一种则是mkdir(),如果父目录不存在则会报错,必须一级一级创建;对于文件类型,则使用createNewFile()方法创建一个新文件。如上代码中dir是个目录,则可以使用listFiles()方法获取目录下的所有子文件和目录,类型是File;其中还有个list()方法,使用它得出来的是个路径地址,类型是String
当然,不仅仅只是这几个操作,File类型还可以获取文件的一系列属性,如length()获取文件大小;需要强调的是File在删除操作中,如果文件夹下还有其他文件夹或者文件,需要递归的删除下面的所有文件才可以使用delete()删除这个文件夹,如下所示:

/**
**示例删除文件夹
**/
public class FileDemo {
    public static void main(String[] arg0) throws IOException{
        File dir = new File("/test");
        if(!dir.exists()){
            dir.mkdirs();
        }
        deleteFile(dir);
    }
    static void deleteFile(File file){
        if(file.isFile()){
            System.out.println("删除文件:"+file.getAbsolutePath());
            file.delete();
        }else if(file.isDirectory()){
            File[] files = file.listFiles();
            if(files!=null){
                for(File f : files){
                    deleteFile(f);
                }
            }
            System.out.println("删除文件夹:"+file.getAbsolutePath());
            file.delete();
        }
    }
}
输出:
删除文件:F:\test\新建 Microsoft Excel 97-2003 工作表.xls
删除文件:F:\test\新建文件夹\新建 Microsoft Excel 97-2003 工作表.xls
删除文件夹:F:\test\新建文件夹\新建文件夹
删除文件夹:F:\test\新建文件夹
删除文件:F:\test\新建文本文档.txt
删除文件夹:F:\test

上面的listFiles()list()方法会为我们列出所有的文件或者文件名,但是通常情况下,我们可能只需要获取满足我们要求的文件,而不需要列出全部。简单的方法:列出来之后,我们再加一层过滤就可以了,其实这个并不需要我们获取之后进行操作,Java提供了这样的一个过滤的接口FileFilterFileNameFilter。我们直接在list()或者listFiles()中使用即可,如:

/**
**文件过滤示例
**/
public class FileDemo {
    public static void main(String[] arg0) throws IOException{
        File dir = new File("/test");
        if(!dir.exists()){
            dir.mkdirs();
        }
        File[] files = dir.listFiles(new FilenameFilter(){
            @Override
            public boolean accept(File dir, String name) {
                // TODO Auto-generated method stub
                if(name.contains("txt")) return true;
                return false;
            }});
        for(File f:files){
            System.out.println(f.getAbsolutePath());
        }
    }
}
输出:
4096
F:\test\test1.txt
F:\test\txt11
F:\test\新建文本文档.txt

在这个示例中,我的文件夹下创建了很多不是txt类型的文件,但是只返回了文件名包含txt的文件。对于接口中的dir指的是文件目录,name指的是文件的简单名,不包含路径。

IO

IO即是输入输出,通常有文件IO,网络IO,标准IO,和系统的内存数组IO。Java使用流来表示数据源对象或者数据接收设备,其中区分输入流InputStream和输出流OutputStream。在JDK 1.1又添加了readerwriter类来操作兼容 unicode 与面向字符的类。下面先从字节IO类说起。

InputStream与OutputStream

InputStreamOutputStream中存在一系列子类,如下图所示:

InputStream&OutputStream.png

其中,类InputStreamOutputStream属于抽象类,并不能将它实例化,如下:

/**
**InputStream和OutputStream的声明
**/
public abstract class InputStream implements Closeable {
    /**field and method **/
}
public abstract class OutputStream implements Closeable, Flushable {
    /**field and method **/
}

我们使用的都是他们的子类,由于并不多,可以一个一个的看过来:

FileInputStream&FileOutputStream

从类名就可以看出来,这个类操作的对象就是File文件,在FileInputStream中存在三个构造器,即,FileInputStream(String name)FileInputStream(File file)FileInputStream(FileDescriptor fdObj),通过传入一个文件的路径String或者文件对象FileFileDescriptor对象来创建它。

ps:简单介绍一下类FileDescriptor,代表的是文件描述符,例如in标准输入的描述符、out标准输出的描述符、error标准错误的输出符,如果FileDescriptor表示的是一个文件的话,可以当做File来对待,但是并不能直接操作,需要通过创建对应的FileInputStreamFileOutputStream,然后再进行操作。

/**
**这里的异常请使用try-catch,并在finally中释放资源,例子中为了方便没有进行这些操作
**/
public class IODemo {
    public static void main(String[] arg0) throws IOException{
        InputStream in = new FileInputStream(new File("/test/test.txt"));
        OutputStream out = new FileOutputStream(new File("/test/demo.txt"));
        int length;
        byte[] buffer = new byte[1024];
        while((length = in.read(buffer))!=-1){
            out.write(buffer,0,length);
        //  System.out.println(new String(buffer,0,length));    
        }
    }
}

在上面的代码中,会将test.txt文件的内容写入demo.txt文件中,使用byte[]数组来进行缓存,其中length的作用是避免最后一次读取缓存区有空余,造成异常IndexOutOfBoundsException。看了下源码,read()以及它重载方法是调用的native方法,不过可以知道,它读取的是字节,而使用byte[]之后,读取的就是这个数组大小的字节数。

ObjectInputStream&ObjectOutputStream

这两个类是直接对对象进行操作的,需要注意的是:对象必须实现Serializable接口,在使用ObjectInputStream时,很容易出异常,因为将实例信息直接存在文件中,如果不清楚文件包含了那些类,很容易出异常。如下所示:

/**
**ObjectInputStream示例
**/
static void testObjectInputStream() throws FileNotFoundException, IOException, ClassNotFoundException{
        File file = new File("/test/test.txt");
        if(!file.exists()){
            file.createNewFile();
        }
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));
        Test test = new Test("demo");
        Test test1 = new Test("demo1");
        out.writeObject(test1);
        out.writeObject(test);
        
        ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
        try {
            Test t = (Test)in.readObject();
            System.out.println(t.getName());
            Test t1 = (Test)in.readObject();
            System.out.println(t1.getName());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
class Test implements Serializable{
    private String name;
    public Test(String name){
        this.name = name;
    }
    public String getName(){
        return name;
    }   
}

其中类ObjectOutputStreamObjectInputStream不能直接创建,需要传入一个OutputStreamInputStream,在读取的时候,直接使用readObject()方法。当然咯,这个类也支持一些基本数据类型的操作。这里其实有些疑问,虽然我们可以读也可以取数据,但是怎么保证我取的数据就是我想要的数据呢?而且该如何判断文件中的数据已经被读完了,难道是以EOFException来判断么?对于这两个问题,我想用个容器,把需要持久化的对象放进去,然后写这个容器就可以了,如果你有更好的方法,请告知。

ByteArrayInputStream&ByteArrayOutputStream

在《Thinking in Java》中有提到,这两个类是针对内存缓存进行操作的,它允许将内存缓存变成一个流。如:

/**
**ByteArrayInputStream&ByteArrayOutputStream示例
**/
static void testByteArray() throws IOException{
    //  ByteArrayInputStream bais = new ByteArrayInputStream();
        int a=1;
        int b = 2;
        String str = "str";
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        baos.write(a);
        baos.write(b);
        baos.write(str.getBytes());
        System.out.println(new String(baos.toByteArray()));
        byte[] buffer = baos.toByteArray();
        ByteArrayInputStream bais = new ByteArrayInputStream(buffer);
        int length = 0;
        while((length=bais.read())!=-1){
            System.out.println(length);
        }
    }
输出:
��str
1
2
115
116
114

可以看到,在上面的代码中,做了两种输出,对于int类型的变量,在流内可以直接获取转换,但是对于byte[]类型,如果和int类型在一个ByteArrayOutputStream一起使用,会出现解析的问题,因为拿到的是一个byte[],所以不知道在哪里才是变量的分界点。这时候我们可以选择和DataOutputStreamDataInputStream配合使用,如:

/**
**配合DataInputStream和DataOutputStream使用
**/
static void testByteArray() throws IOException{
        int a=1;
        int b = 2;
        double k= 8.00;
        String str = "str";
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        DataOutputStream dos = new DataOutputStream(baos);
        dos.write(a);
        dos.write(b);
        dos.writeDouble(k);
        dos.writeUTF(str);
        System.out.println(new String(baos.toByteArray()));
        byte[] buffer = baos.toByteArray();
        ByteArrayInputStream bais = new ByteArrayInputStream(buffer);
        DataInputStream dis = new DataInputStream(bais);
//      System.out.println(dis.readDouble());
        System.out.println(dis.read());
        System.out.println(dis.read());
        System.out.println(dis.readDouble());
        System.out.println(dis.readUTF());
    }
输出:
��@ 
1
2
8.0
str

在上面的代码中,有点需要注意的是:read()方法的获取顺序,要和存放的顺序一致,不然会出现转码问题或者异常。这样就解决了上面那个字节和字节数组冲突的问题,而且DataOutputStream提供的操作方法并不仅仅是这两个,它提供了一系列的重载方法。

ps:DataInputStreamDataOutputStream都属于FilterInputStreamFilterOutputStream下的子类,这个FilterIn/OutputStream是IO实现装饰器模式的关键,在接下来的会说明。

FilterInputStream&FilterOutputStream

在上面那个例子中,使用了DataInputStramDataOutputStream,所以这里来理解一下FilterInputStreamFilterOutputStream,因为这两个类确实非常重要,它是用来提供装饰器类接口以控制特定输入输出流,在FilterIn/OutputStream下对应存在四个子类,其中LineNumberIn/OutputStream已经过期。这里介绍一下:
DataInputStream&DataOutputStream
DataOutputStream能将基本数据类型或String对象格式化到流中,就像上个例子一样,任何机器上的任何地方DataInputStream都可以通过这个获取到的byte[]把数据获取到。
BufferInputStream&BufferOutputStream
这两个类为IO提供了缓存的功能,它使得我们操作的对象变成BufferIn/OutputStream中的成员byte[] buf,在使用BufferInputStream时,会将这个类的buf数组填充满,使用read()方法时,会先从这里读取,类似的BufferOutputStream也是这样,操作的时候,从buf中先操作好了,再write()出去。
PushbakInputStream
这个类能弹出一个字节的缓冲区,因此能将读到的最后一个字节回退,通常作为编辑器的扫描器,之所以包含在这里是因为java编译器需要,了解一下就可以了。
PrintStream
这个类其实应该是最熟悉的,我们的System.out就是使用的这个类,它提供了两个方法println()print()

在上面这几个类都是FilterInputStream或者FilterOutputStream子类。都可以传入相应的InputStreamOutputStream来构造新的类。

PipedInputStream&PipedOutputStream

管道输入输出流,这两个类配合可以实现线程间通信。实现的流程如下:

/**
**PipedInputStream&PipedOutputStream示例
**/
public class PipedDemo {
    public static void main(String[] arg0) throws IOException{
        PipedOutputStream out = new PipedOutputStream();
        PipedInputStream in = new PipedInputStream();
        out.connect(in);
        new Thread(new Product(out)).start();
        new Thread(new Consumer(in)).start();
        
    }
}
class Product implements Runnable{
    PipedOutputStream out;
    public Product(PipedOutputStream out){
        this.out = out;
    }
    @Override
    public void run() {
        // TODO Auto-generated method stub
        String test = "hello world";
        try {
            out.write(test.getBytes());
            out.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
    }
}
class Consumer implements Runnable{
    PipedInputStream in;
    public Consumer(PipedInputStream in){
        this.in = in;
    }
    @Override
    public void run() {
        // TODO Auto-generated method stub
        byte[] buff = new byte[1024];
        try {
            int len = in.read(buff);
            System.out.println(new String(buff,0,len));
            in.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
    }
}
输出:
hello world

可以发现,我在其他线程中写入的数据,在另一个绑定了这个out的线程PipedInputStream中也能获取到对应的数据。个人感觉有一点不好的是,这里并不能将一个out绑定多个in,在多个线程中使用同一个绑定的in也会出现异常StringIndexOutOfBoundsException

SequenceInputStream

这个类可以将多个InputStream合并到一起操作,提供了两个构造方法,一个是SequenceInputStream(InputStream s1, InputStream s2),另一个使用枚举类型SequenceInputStream(Enumeration<? extends InputStream> e),这样可以达到合并流的效果。当然不是无缘无故合并的,SequenceInputStream将与之相连接的流集组合成一个输入流并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。如下所示:

/**
**SequenceInputStream示例
**/
public class SequenceDemo {

    public static void main(String[] arg0) throws IOException{
        SequenceInputStream sis = null;
        Enumeration<InputStream> inputStreamEnum;       
        Vector<InputStream> inputStreamV = new Vector<InputStream>();
        inputStreamV.add(new FileInputStream("/test/test.txt"));
        inputStreamV.add(new FileInputStream("/test/demo.txt"));
        inputStreamEnum = inputStreamV.elements();
        
        sis = new SequenceInputStream(inputStreamEnum);
        byte[] buff = new byte[1024];
        int len;
        while((len=sis.read(buff))!=-1){
            System.out.println(new String(buff,0,len));
        }
    }
}
输出:

sdasd
kkk
sdjanb

虽然上面的乱码是因为之前例子存放的是对象的实例,无法直接new String()出来,但是可以把代码中的FileInputStream换一下就好了。大体上可以看出,SequenceInputStream类将多个InputStream合并在一起,操作的时候顺序读取。

这里把大部分的字节流操作类给介绍了,在图中的StringBufferInputStream没有介绍,因为已经过时了,并不推介去用,把字节流梳理完了,下面会看字符流操作。

这部分以前看过,但是理解的并不深刻,现在整理之后效果还是挺好的,由于本人能力有限,文中出现的问题请帮忙指正~
文章参考:《Thinking in Java》18章Java I/O系统
《Java程序设计语言》20章 IO包
Java IO最详解

相关文章

  • Java梳理之理解IO(一)

    对于IO部分,写的并不多,通常情况下都是复用现有的代码,致使很多细节都理解的并不透彻,现在梳理一下字节字符流。 前...

  • Java梳理之理解IO(二)

    在上一篇文章中,梳理了IO的字节流,现在接着梳理字符流,如果文中有任何不对的地方,麻烦帮忙指出~ 在《Java编程...

  • Java之IO初理解

    最近socket让我不能理解,上一节路由课,看了网络编程的视频,深入思考了一下下,给自己一个想通的理由哈哈哈 总结...

  • Java之IO流详解

    title: Java之IO流详解tags: Java IO流categories: Java IO流 大多数应用...

  • 【Java梳理】IO流

    IO即输入输出的缩写。在程序中输入输出数据是很常见的操作。在Java中经常使用的方式是流(Stream)。流是操作...

  • 理解java IO

    I/O 问题是任何编程语言都无法回避的问题,可以说 I/O 问题是整个人机交互的核心问题,因为 I/O 是机器获取...

  • Java学习总结之Java IO系统(二)

    本文接着Java学习总结之Java IO系统(一),我们继续总结Java IO系统的相关知识。 字符流(Write...

  • 彻底理解Java IO

    Java中的IO类库设计的比较繁琐,IO这一块知识又是基础必备的,而且工作学习中经常用到。这一块知识看起来不难,但...

  • SAP FSCD 业务

    BP IO CA 一一梳理学习,从业务层面去理解系统。换一种思维

  • java文艺复兴

    JAVA 语言基础 理解面向对象OO 常用类 集合 IO 多线程 GUI(??) 网络编程 JAVA WEB (...

网友评论

      本文标题:Java梳理之理解IO(一)

      本文链接:https://www.haomeiwen.com/subject/xyffuxtx.html