前言

在前面的学习中,我们已经接触了 Java 中最基础的 IO 流,比如 FileInputStreamFileOutputStreamFileReaderFileWriter。这些流可以完成最基本的文件读写操作,但是当文件变大、读写操作变多之后,就会发现普通流虽然能用,但是效率并不高。

所以这篇文章,我们就来学习 IO 流中非常重要的一部分内容:缓冲流

我们主要学习下面几种缓冲流:

  • BufferedInputStream
  • BufferedOutputStream
  • BufferedReader
  • BufferedWriter

这篇文章将重点总结一下缓冲字节输入输出流、缓冲字符输入输出流的特点,以及它们在实际开发中的常见使用方式。


引入

如果把普通流比作我们一趟一趟自己去搬东西,那么缓冲流就像给我们配了一辆小推车。

以前一次只能搬一点,现在一次可以搬更多。
来回跑的次数少了,效率自然也就提高了。

所以缓冲流最核心的思想就是:

在原有流的基础上增加缓冲区,减少和磁盘频繁交互的次数,从而提高读写效率。


一、为什么要学缓冲流

前面学过的普通流可以完成文件读写,但是它们往往存在两个问题:

  1. 读写效率不够高
  2. 处理文本时不够方便

而缓冲流正好可以解决这两个问题。

缓冲流可以分成两类:

  • 缓冲字节流:适合处理所有类型文件,尤其适合复制图片、视频、音频等二进制文件
  • 缓冲字符流:适合处理文本文件,读写更方便

二、缓冲字节流

1.BufferedInputStream 和 BufferedOutputStream 的基本使用

在项目中,缓冲字节流的使用方式如下:

InputStream src = new FileInputStream(srcpath);
InputStream bic = new BufferedInputStream(src);

OutputStream dest = new FileOutputStream(sicpath);
OutputStream bit = new BufferedOutputStream(dest);

byte[] bytes = new byte[1024];
int len;
while ((len = bic.read(bytes)) != -1) {
    bit.write(bytes, 0, len);
}

从这段代码可以看出:

  • FileInputStreamFileOutputStream 负责真正和文件建立连接
  • BufferedInputStreamBufferedOutputStream 负责在外面包一层缓冲,提高效率

也就是说:

缓冲字节流本质上是包装流,它必须依赖基础字节流使用。


2、缓冲字节流的特点

2-1. 自带缓冲区,读写效率更高

缓冲字节流内部自带缓冲池,不会每次都直接和磁盘发生交互,而是会先把一部分数据读到内存中,再从内存中取数据使用。

这样做的好处有:

  • 减少磁盘访问次数
  • 提高文件复制速度
  • 适合频繁读写场景

这也是缓冲字节流和普通字节流最核心的区别。


2-2. 适合处理所有类型文件

因为字节流本身就适合处理任意类型文件,所以缓冲字节流同样适合:

  • 图片
  • 视频
  • 音频
  • 压缩包
  • 可执行文件
  • 任意二进制文件

2-3. 配合字节数组使用效果最好

虽然缓冲字节流本身已经提升了效率,但是如果仍然坚持“一个字节一个字节”地读写,性能依然不理想。

四种复制方式:

  • 普通字节流,一个字节一个字节复制
  • 普通字节流,按字节数组复制
  • 缓冲字节流,一个字节一个字节复制
  • 缓冲字节流,按字节数组复制

最推荐的方式是:

InputStream fis = new FileInputStream(SRC_FILE);
InputStream bis = new BufferedInputStream(fis);
OutputStream fos = new FileOutputStream(DEST_FILE + "4.avi");
OutputStream bos = new BufferedOutputStream(fos);

byte[] bytes = new byte[1024 * 32];
int len;
while ((len = bis.read(bytes)) != -1) {
    bos.write(bytes, 0, len);
}

结论:

缓冲流 + 数组读写,才是处理大文件时更推荐的组合。


三、缓冲字符流

缓冲字符流和缓冲字节流的核心思想一样,都是通过缓冲区提升效率。
不同点在于,字符流是专门面向文本内容的,所以它们更适合处理纯文本文件。


1.BufferedReader 的特点

1. 基本使用

Reader reader = new FileReader("day03-File-io\\src\\zifuxiaole2");
BufferedReader br = new BufferedReader(reader);

String line;
while ((line = br.readLine()) != null) {
    System.out.println(line);
}

2. 核心特点

(1)本质上也是包装流

和缓冲字节流一样,BufferedReader 也不是独立存在的,它是建立在 Reader 基础上的增强流。

常见写法就是:

Reader reader = new FileReader(...);
BufferedReader br = new BufferedReader(reader);

所以可以总结一句:

高级流一般都是对低级流做增强。


(2)读取文本效率更高

BufferedReader 内部同样带有缓冲区,处理字符数据时比普通 FileReader 更高效。

尤其适合读取:

  • .txt
  • .java
  • .xml
  • .properties
  • .md

这类纯文本文件。


(3)支持按行读取

这是 BufferedReader 最实用的特点之一:

br.readLine()

普通 FileReader 更多是按字符读取,而 BufferedReader 可以直接按行读取文本。

所以在处理:

  • 日志文件
  • 配置文件
  • 代码文件
  • 普通文本

时,BufferedReader 会更方便。


四、BufferedWriter 的特点

1. 基本使用

Writer fr = new FileWriter("day03-File-io/src/itheima/Demo11BufferWriter/xiaole-out2", true);
BufferedWriter br = new BufferedWriter(fr);

br.write('a');
br.write(98);
br.write("Java");
br.newLine();

char[] chars = "java".toCharArray();
br.write(chars);

2. 核心特点

(1)写文本效率更高

BufferedWriter 本质上就是在字符输出流的基础上增加缓冲机制,减少频繁写磁盘的次数,所以写文本时效率更高。


(2)支持多种写出方式

项目中演示了它可以:

  • 写单个字符
  • 写字符串
  • 写字符数组
  • 写字符串的一部分
  • 写字符数组的一部分

这一点说明 BufferedWriter 在处理文本时非常灵活。


(3)支持 newLine()

项目里多次用到了:

br.newLine();

它的好处是:

  • 不需要手动写 \r\n
  • 代码更清晰
  • 可读性更好

所以在写多行文本时,BufferedWriter 会比普通写法更舒服。


(4)要注意刷新和关闭

缓冲输出流的数据不一定会立刻写入目标文件,它可能先停留在缓冲区中。

所以这里要注意两个方法:

  • flush():刷新缓冲区,但流还能继续使用
  • close():关闭流,关闭前通常会自动刷新

开发中更推荐使用 try-with-resources,这样更安全。


五、缓冲字节流和缓冲字符流的区别

对比项 缓冲字节流 缓冲字符流
代表类 BufferedInputStream BufferedOutputStream BufferedReader BufferedWriter
处理单位 字节 字符
适用文件 所有文件 文本文件
是否适合图片/视频 适合 不适合
是否支持按行读取 不支持 BufferedReader 支持
是否支持便捷换行 一般 BufferedWriter.newLine() 很方便
核心优势 复制文件快 处理文本方便

一句话总结:

  • 复制任意文件,优先考虑缓冲字节流
  • 处理文本内容,优先考虑缓冲字符流

六、延伸:缓冲流和转换流经常配合使用

InputStream inputStream = new FileInputStream("day03-File-io\\src\\zifuxiaole3");
Reader isr = new InputStreamReader(inputStream, "GBK");
BufferedReader reader = new BufferedReader(isr);

这段代码说明在实际开发中,很多时候我们并不是直接:

FileReader + BufferedReader

而是:

InputStream + InputStreamReader + BufferedReader

这样做有两个好处:

  1. 指定编码,避免乱码
  2. 提高读取效率

所以可以总结一句经验:

缓冲流除了提升效率之外,还经常作为最外层包装流使用。


七、使用建议

结合这次内容,我总结了下面几点:

1. 复制文件时尽量用字节数组

byte[] buffer = new byte[1024 * 8];
int len;
while ((len = bis.read(buffer)) != -1) {
    bos.write(buffer, 0, len);
}

不要总是一个字节一个字节读写,效率太低。


2. 处理文本优先考虑缓冲字符流

因为它们:

  • 读得快
  • 写得方便
  • 支持按行读取
  • 支持便捷换行

3. 输出流一定要记得刷新和关闭

特别是缓冲输出流,如果没有正常关闭,数据可能还停留在缓冲区里。


总结

缓冲流并不是一种新的 IO 思路,而是在原有基础流之上增加了一层缓冲机制,让读写更加高效、更加方便。

最后再总结一下缓冲流最值得记住的几个特点:

  • BufferedInputStreamBufferedOutputStream 适合处理任意文件
  • BufferedReaderBufferedWriter 适合处理文本文件
  • BufferedReader 支持按行读取
  • BufferedWriter 支持 newLine()
  • 缓冲流本质上是包装流
  • 真正常用的高效写法一般是 缓冲流 + 数组

如果刚开始学 IO,我觉得缓冲流这一块一定要多敲几遍,因为后面做文件复制、日志处理、配置文件读取时都会经常遇到。


更多推荐