美文网首页每天写1000字
Java IO 第2篇:IO 流,掌控一切

Java IO 第2篇:IO 流,掌控一切

作者: 追梦人_奋斗青年 | 来源:发表于2020-04-16 20:05 被阅读0次

    IO 流,掌控一切

    上一篇文章我们认识了文件操作的源头 File 类,这篇文章就来聊聊文件操作的核心 IO 流。
    我们经常可以听到:输入流、输出流、字节流、字符流、节点流、处理流等词语,咋一听,忍不住“哇~~~!”的一声,心里在想:“感觉好复杂的样子,学习 IO 流需要知道这么多东西啊!”,从而有了畏难的情绪。大家千万不要被这些词语吓到,望而却步,它们只不过是从三个维度对 IO 流的总结。
    学习 IO 流是有套路的,通过这篇文章的学习,你一定能掌握 IO 流的使用技巧,从而掌控一切文件操作问题。

    一、认识 IO 流

    1、IO 流的分类

    从 IO 流的流向来划分,IO 流分为:输入流、输出流。

    从 IO 流要处理的数据来划分,IO 流分为:字节流、字符流。其中,字节流可以处理一切文件数据,包括纯文本,word文档,pdf文档,图片,音频和视频等二进制数据;字符流只能处理纯文本文件。

    从 IO 流的功能来划分,IO 流分为:节点流和处理流。其中,节点流是用来包装数据源(File)的,它直接和数据源连接,表示从一个节点读取数据或者把数据写入到一个节点;处理流是用来包装节点流的,它是对一个已经存在的节点流进行连接,处理流通过增加缓存的方式来提高输入输出操作的性能。

    总的来说,java.io 包中流的操作主要分为字节流和字符流两类,他俩都有对应的节点流与数据源进行连接,为了提高文件操作的性能,在节点流的基础上提供了处理流,以便增强节点流的功能,同时他俩都有输入和输出操作。

    通过上面的分类,大家先对 IO 流先有一个初步的了解,后面结合代码给大家进一步讲解。

    2、区分流的输入与输出

    在程序中所有的数据都是以流的方式进行传输的,程序需要数据的时候就用输入流读取数据,当程序需要将计算好的数据进行保存到文件或者输出到其他系统时,就用输出流写出数据。

    简单来说的话,就是以我们的程序为中心,如果是外部的数据流向程序,那么就是输入流,输入流一定是读取操作;如果是程序里的数据流出到外部,那么就是输出流,输出流一定是写出操作。

    输入输出流与文件和程序的关系示意图

    3、IO 操作的套路

    Java 中 IO 操作也是有套路的,有标准的操作步骤,主要的操作步骤如下:

    1、使用 File 类与文件建立联系

    2、选择对应的输入流或者输出流

    3、进行读或写操作

    4、关闭资源

    先对这个套路进行一个了解,后面结合代码一下就明白了,原来套路如此简单。

    二、万能钥匙字节流

    1、认识字节流

    字节流主要操作 byte 类型数据,说它是万能钥匙,是因为它可以处理一切文件,包括文本、word文档、Excel文档、pdf文档、图片、语音、视频等,统统都可以处理。

    字节流分为字节输入流和字节输出流,在 Java 中 字节输入流用 InputStream 表示,字节输出流用 OutputStream 表示。

    字节输入流:InputStream 是一个抽象类,必须依靠其子类 FileInputStream 来读取文件内容,输入到程序中。我们常用的方法是:

    int read(byte b[]) //读取byte数组中的内容,返回读入的长度
    close() //关闭资源
    

    字节输出流:OutputStream 是一个抽象类,必须依靠其子类 FileOutputStream 来读取文件内容,输入到程序中。我们常用的方法是:

    //将一个制定范围的byte数组输出
    void write(byte b[], int off, int len) 
    close() //关闭资源
    flush() // 在关闭资源的时候默认会调用刷新方法
    

    2、字节输出流 FileOutputStream 的使用

    我们来看一个例子,把“演示字节输出流的使用\r\n用 FileOutputStream 类操作!”的文本输出到 D:/file/txt/output.txt 文件中。

    因为文件操作有可能发生 FileNotFoundException 和 IOException,为了精简代码,便于阅读主要代码,除了本例子以外,后续的例子我会直接使用 throws 关键字抛出异常,并且关闭资源也不放在finally里,这样可以减少 try...catch...finally的代码。

    @Test
      public void testOutput() {
        // 1、建立联系, File对象, 输出文件的地址
        // 如果文件不存在则可以创建文件并写入,
        // 但是如果加了文件夹,那么文件夹不存在则会产生FileNotFoundException,系统找不到指定的路径
        String path = "D:/txt/output.txt";
        File file = new File(path);
        // 2、选择流
        // 由于os要在finally中用到,放到try的外部,以提升os的变量作用范围
        OutputStream os = null;
        try {
          // 用FileOutputStream子类实例化父类OutputStream
          // 以追加的方式输出到文件,必须是true,否则就会覆盖原有的文件
          os = new FileOutputStream(file, true);
          // 3、操作
          String info = "演示字节输出流的使用\r\n用 FileOutputStream 类操作!\r\n";
          byte[] b = info.getBytes();// 字符串转字节数组
          os.write(b, 0, b.length);// 写出
          // 要养成这个习惯,为了避免缓存没有写出去,需要显示地flush一下
          os.flush();
        } catch (FileNotFoundException e) {
          e.printStackTrace();
          System.out.println("文件不存在");
        } catch (IOException e) {
          e.printStackTrace();
          System.out.println("文件写出失败");
        } finally {
          try {
            // 4、释放资源
            if (os != null) {
              os.close();
            }
          } catch (Exception e2) {
            System.out.println("关闭文件输出流资源失败");
          }
        }
      }
    

    运行结果:


    字节输出流 FileOutputStream 的使用

    3、字节输入流 FileInputStream 的使用

    上面的例子我们学会了字节输出流的使用,下面用字节输入流 FileInputStream 来读取上面的文件内容。

    @Test
      public void testInput() throws IOException {
        // 1、建立联系
        File file = new File("D:/output.txt");
        // 2、选择流
        InputStream is = new FileInputStream(file);
        // 3、读操作:即不断地读取
        byte[] b = new byte[1024]; // 缓存数组
        int len = 0; // 接收实际读取的大小
        while ((len = is.read(b)) != -1) {
          // 能读取到数据则输出,字节数组转成字符串
          String info = new String(b, 0, len);
          System.out.println(info);
        }
        is.close();
      }
    

    运行结果:

    演示字节输出流的使用
    用 FileOutputStream 类操作!
    演示字节输出流的使用
    用 FileOutputStream 类操作!
    

    4、使用字节流,完成图片文件的拷贝

    下面的例子演示如何通过字节流对图片文件进行拷贝操作,假设把 tomcat.png 拷贝成 tomcat1.jpg。

    文件的拷贝操作的思路就是,用字节输入流读取图片 tomcat.png 的内容,用字节输出流写出到 tomcat1.jpg 文件中,根据文件操作的套路,很容易就能写出以下的代码:

    @Test
      public void testCopy() throws IOException {
        // 1、使用File类与文件建立联系
        File srcFile = new File("D:/file/image/tomcat.png");
        File destFile = new File("D:/file/image/tomcat1.jpg");
        // 2、选择对应的输入流或者输出流
        InputStream is = new FileInputStream(srcFile);
        OutputStream os = new FileOutputStream(destFile);
          // 3、进行读或写操作
          byte[] b = new byte[1024];
          int len = 0;
          while ((len = is.read(b)) != -1) {
            // 判断每次读取的内容长度,如果不等于-1,表示文件没有读完
            // 选择带参数的write方法,就是为了避免byte缓存比实际内容多的时候,输出多余的空内容
            os.write(b, 0, len);
          }
        os.flush();
        // 4、关闭资源,先创建的后关闭
        os.close();
        is.close();
      }
    

    运行结果:


    使用字节流,完成图片文件的拷贝

    三、纯文本操作字符流

    1、认识字符流

    字符流主要操作纯文本类型数据,只能处理 txt、html 等文本类型的数据,在程序中一个字符等于两个字节,Java 提供了 Reader 类和 Writer 类用于专门操作字符流。

    字符流也分为字符输入流和字符输出流,在 Java 中 字符输入流用 Reader 表示,输出流用 Writer 表示。

    字符输入流:Reader 是一个抽象类,必须依靠其子类 FileReader 来读取纯文本文件内容,输入到程序中。我们常用的方法是:

    int read(char cbuf[]) //读取char数组中的内容,返回读入的长度
    close() //关闭资源
    

    字符输出流:Writer 是一个抽象类,必须依靠其子类 FileWriter 来读取纯文本文件内容,输入到程序中。我们常用的方法是:

    //将一个字符串输出
    void write(String str)
    //将一个字符数组输出
    void write(char cbuf[], int off, int len)
    close() //关闭资源
    flush() // 在关闭资源的时候默认会调用刷新方法
    

    2、字符输出流 FileWriter 的使用

    我们来看一个例子,把“演示字符输出流的使用\r\n用 FileWriter 类操作!”的文本输出到 D:/file/txt/output_char.txt 文件中。

    @Test
      public void testWriter() throws IOException {
        // 1、使用File类与文件建立联系
        File file = new File("D:/file/txt/output_char.txt");
        // 2、选择对应的输入流或者输出流
        Writer writer = new FileWriter(file, true);
        String info = "演示字符输出流的使用\r\n用 FileWriter 类操作!\r\n";
        // 3、进行写操作
        writer.write(info); //将一个字符串组输出
        writer.flush();
        // 4、关闭资源
        writer.close();
      }
    

    运行结果:


    字符输出流 FileWriter 的使用

    3、字符输入流 FileReader 的使用

    上面的例子我们学会了字符输出流的使用,下面用字符输入流 FileReader 来读取上面的文件内容。

    @Test
      public void testReader() throws IOException {
        // 1、使用File类与文件建立联系
        File file = new File("D:/file/txt/output_char.txt");
        // 2、选择对应的输入流或者输出流
        Reader reader = new FileReader(file);
        char[] cbuf = new char[1024];
        int len = 0;
        // 3、进行写操作
        while ((len = reader.read(cbuf)) != -1) {
          String info = new String(cbuf, 0, len); // 字符数组转成字符串
          System.out.println(info);
        }
        // 4、关闭资源
        reader.close();
      }
    

    运行结果:

    
    演示字符输出流的使用
    用 FileWriter 类操作!
    演示字符输出流的使用
    用 FileWriter 类操作!
    

    4、利用字符流,完成 txt文本文件的拷贝

    下面的例子演示如何通过字符流对图片文件进行拷贝操作,把 output_char.txt 拷贝成 output_char1.txt。

    @Test
      public void testTxtCopy() throws IOException {
        // 1、使用File类与文件建立联系
        File srcFile = new File("D:/file/txt/output_char.txt");
        File destFile = new File("D:/file/txt/output_char1.txt");
        // 2、选择对应的输入流或者输出流
        Reader read = new FileReader(srcFile);
        Writer write = new FileWriter(destFile);
        // 3、进行读写操作
        char[] cbuf = new char[1024];
        int len = 0;
        while ((len = read.read(cbuf)) != -1) {
          write.write(cbuf, 0, len); //将一个字符数组输出
        }
        write.flush();
        // 4、关闭资源
        write.close();
        read.close();
      }
    

    运行结果:

    利用字符流,完成 txt文本文件的拷贝

    四、字节流与字符流的区别

    1、字符输出流在写出文件时用到了缓存区

    除去刚才讲过的,字节流可以处理一切文件,字符流只能处理纯文本文件,两者还有一个明显的差异,那就是字符输出流在操作文件时使用了缓冲区,通过缓冲区再写出到文件,而字节输出流直接操作文件。

    1、通过源码可以证明字符输出流用到了缓存区

    通过源码可以证明字符输出流用到了缓存区

    2、通过两段代码的输出结果证明字符输出流用到了缓存区

    • 验证字符流:
    /**
       * 把flush方法和close方法去掉,观察程序运行结果,用字符流输出内容到文件是空的
       */
      @Test
      public void testWriter1() throws IOException {
        // 1、使用File类与文件建立联系
        File file = new File("D:/file/txt/output_char_buffer.txt");
        // 2、选择对应的输入流或者输出流
        Writer writer = new FileWriter(file, true);
        String info = "把flush方法和close方法去掉,观察程序运行结果,输出的内容文件是空的!\r\n";
        // 3、进行写操作
        writer.write(info);
      }
    

    运行结果:

    验证字符输入流用到了缓存区
    • 验证字节流:
    /**
       * 把flush方法和close方法去掉,观察程序运行结果,用字节流可以输出内容到文件
       */
      @Test
      public void testOutput1() throws IOException {
        // 1、使用File类与文件建立联系
        File file = new File("D:/file/txt/output_char_output.txt");
        // 2、选择对应的输入流或者输出流
        OutputStream os = new FileOutputStream(file, true);
        // 3、进行写操作
        String info = "把flush方法和close方法去掉,观察程序运行结果,输出的内容文件是空的!\r\n";
        byte[] b = info.getBytes();// 字符串转字节数组
        os.write(b, 0, b.length);// 写出
      }
    

    运行结果:

    验证字节输入流不用缓存区

    通过以上的 2 段程序,可以看出,字符流是有缓存的,如果我们没有调用 flush 方法,并且没有调用 close 方法,是无法把内容写到文件中的。但是同样的没有调用 flush 方法和 close 方法,字节流确可以把内容写出到文件。

    • 验证字符流调用 flush方法,不调用 close 方法的结果
    /**
       * 调用flush方法,不调用close方法,观察程序运行结果,用字符流输出内容到文件是可以的,说明字符输出流确实用到了缓冲区
       */
      @Test
      public void testWriter2() throws IOException {
        // 1、使用File类与文件建立联系
        File file = new File("D:/file/txt/output_char_writer.txt");
        // 2、选择对应的输入流或者输出流
        Writer writer = new FileWriter(file);
        String info = "调用flush方法,不调用close方法,观察程序运行结果,用字符流输出内容到文件是可以的,说明字符输出流确实用到了缓冲区!\r\n";
        // 3、进行写操作
        writer.write(info);
        // 4、强制刷出
        writer.flush();
      }
    

    运行结果:

    验证字符流调用 flush方法,不调用 close 方法的结果
    • 验证字符流调用 close 方法,不调用 flush 方法的结果
    /**
       * 调用close方法,不调用flush方法,观察程序运行结果,用字符流输出内容到文件是可以的,说明字符输出流确实用到了缓冲区
       */
      @Test
      public void testWriter3() throws IOException {
        // 1、使用File类与文件建立联系
        File file = new File("D:/file/txt/output_char_writer.txt");
        // 2、选择对应的输入流或者输出流
        Writer writer = new FileWriter(file);
        String info = "调用close方法,不调用flush方法,观察程序运行结果,用字符流输出内容到文件是可以的,说明字符输出流确实用到了缓冲区!\r\n";
        // 3、进行写操作
        writer.write(info);
        // 4、关闭资源
        writer.close();
      }
    

    运行结果:

    验证字符流调用 close 方法,不调用 flush 方法的结果

    通过以上的 2 段程序,可以看出,字符流是有缓存的,通过显示调用 flush 方法可以把缓存内容输出到文件,如果没有调用 flush 方法,在调用 close 方法时,默认也是会把缓存内容输出到文件。

    切记字符输出流在flush方法和close方法都没有调用的时候,是无法输出内容到文件的。为了避免出现此类问题,我们在使用输出流的时候,不管是字节流还是字符流最好都显示的调用一下 flush 方法。

    讲了这么多,大家觉得我们在操作文件的时候是用字节流好呢还是用字符流好呢,答案是使用字节流更好,因为所有的文件在磁盘中以及网络传输都是以二进制的字节传输的,所以在实际开发中,字节流用的比较广泛

    我们再来明确一下,文件操作的套路只有4步:

    1、使用File类与文件建立联系
    2、选择对应的输入流或者输出流
    3、进行读或写操作
    4、关闭资源

    另外读写操作也是有固定套路的:

     byte[] b = new byte[1024];
        int len = 0;
        while ((len = is.read(b)) != -1) {
          os.write(b, 0, len);
     }
    

    相关文章

      网友评论

        本文标题:Java IO 第2篇:IO 流,掌控一切

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