暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

揭秘文件操作精髓:深入探索IO流的奥秘与力量!

追梦Java 2024-07-15
64

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


1

认识 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、关闭资源


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


2

万能钥匙字节流

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("关闭文件输出流资源失败");
        }
        }
        }

        运行结果:


        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();
              }

              运行结果:

              3

              纯文本操作字符流


              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();
                    }

                    运行结果:


                    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();
                          }

                          运行结果:

                          4

                          字节流与字符流的区别


                          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();
                                }


                                运行结果:
                                • 验证字符流调用 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();
                                  }

                                  运行结果:


                                  通过以上的 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,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                                    评论