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

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

1

认识 IO 流

a6500c1364208b02f9fdca85655183d8.png

1、IO 流的分类

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

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

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

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

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

2、区分流的输入与输出

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

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

2d5b31871f0c8c92108dc704406fc206.png

3、IO 操作的套路

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

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

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

3、进行读或写操作

4、关闭资源

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

2

万能钥匙字节流

a4e86d8ad13afef90587829b3138e856.png

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

运行结果:

c2ec3092eddf43c6f06a6db68e9dde24.png

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

运行结果:

9dcffb888f9a56b7ac8a6fff4a1a954f.png

3

纯文本操作字符流

d762eef1e75f0341bf204fa3ff60f665.png

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

运行结果:

10db24d32039e7901b28576680c00f51.png

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

运行结果:

3c3b2496c77f0c76245c1543dbd80faa.png

4

字节流与字符流的区别

08e01ae9adf372304e2438f891cb3c77.png

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

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

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

e5ff8ba14347bc563df863806a213a51.png

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

运行结果:

ae3650a08a2ab8c1e7d9553533d2a7e1.png

  • 验证字节流:

/**
   * 把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);// 写出
  }

运行结果:

4e3d83f4ce7fecab6827736933779d9c.png

通过以上的 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();
  }
运行结果:

5758cc4fdf94c0022b1604c49b10f31a.png

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

运行结果:

74f9b59270b45ea30ac9259b103c32bc.png

通过以上的 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);
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值