Java缓冲流与转换流全面解析
作者:许彰午
前言
上一篇我们学习了Java IO流的体系框架和基础用法。在实际开发中,直接使用FileInputStream和FileReader进行逐字节或逐字符读写效率较低,因为每次read操作都会触发一次磁盘IO。Java提供了缓冲流和转换流来解决性能和编码问题。缓冲流通过内置缓存区减少实际IO次数,转换流则在字节流和字符流之间架起桥梁。
理解这两类流是掌握Java IO"装饰器模式"的关键一步。如果把基础流(FileInputStream等)看作"水管",缓冲流就是"水塔"——先把水存储起来,再批量传输;转换流则是"翻译官"——帮你在字节和字符之间进行编码转换。两者结合使用,构成了Java IO编程中最常见的实践模式。本文将深入讲解这两类流的设计原理、使用方法和性能对比。
一、缓冲流的原理
普通IO流每次读/写操作都需要与底层设备交互,产生大量系统调用,性能较低。缓冲流内部维护了一个缓冲区数组。为了理解缓冲流的重要性,可以做一个类比:如果没有缓冲,就相当于每次去银行只取1块钱,一天要去100次;有了缓冲,就是一次取100块钱,只去1次。系统调用的开销是IO性能的主要瓶颈,而缓冲流正是通过"批处理"来减少这个开销。
- 读操作:一次性从磁盘读取大量数据到缓冲区,后续read直接从缓冲区获取,减少磁盘IO
- 写操作:数据先写入缓冲区,当缓冲区满或调用flush()时才一次性写入磁盘
// 缓冲流工作原理示意(伪代码)
class BufferedInputStream extends FilterInputStream {
byte[] buf = new byte[8192]; // 默认8KB缓冲区
int count; // 缓冲区有效字节数
int pos; // 当前读取位置
public int read() {
if (pos >= count) {
fill(); // 缓冲区空了,从底层流填充
}
return buf[pos++];
}
}缓冲区大小默认是8192字节(8KB),可以通过构造方法自定义。
二、字节缓冲流
2.1 BufferedInputStream
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
public class BufferedInputStreamDemo {
public static void main(String[] args) {
long start, end;
// 不使用缓冲流读取
start = System.currentTimeMillis();
try (FileInputStream fis = new FileInputStream("D:/test/large_file.dat")) {
int data;
while ((data = fis.read()) != -1) {
// 逐字节读取(极慢)
}
} catch (IOException e) {
e.printStackTrace();
}
end = System.currentTimeMillis();
System.out.println("无缓冲耗时:" + (end - start) + "ms");
// 使用缓冲流读取
start = System.currentTimeMillis();
try (BufferedInputStream bis = new BufferedInputStream(
new FileInputStream("D:/test/large_file.dat"))) {
int data;
while ((data = bis.read()) != -1) {
// 同样逐字节读取,但背后有缓冲区(快很多)
}
} catch (IOException e) {
e.printStackTrace();
}
end = System.currentTimeMillis();
System.out.println("有缓冲耗时:" + (end - start) + "ms");
}
}装饰器模式体现在此:BufferedInputStream包装了FileInputStream,在不改变原有接口的前提下增强了功能。
2.2 BufferedOutputStream
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class BufferedOutputStreamDemo {
public static void main(String[] args) {
try (BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream("D:/test/buffer_output.txt"))) {
for (int i = 0; i < 1000; i++) {
bos.write(("第" + (i + 1) + "行数据\r\n").getBytes());
// 数据先写入缓冲区,减少磁盘IO次数
}
// flush()将缓冲区数据强制写入磁盘
bos.flush();
System.out.println("批量写入完成!");
} catch (IOException e) {
e.printStackTrace();
}
}
}注意:close()方法会自动调用flush(),所以使用try-with-resources时通常不需要手动调用flush()。
三、字符缓冲流
3.1 BufferedReader
BufferedReader是字符缓冲输入流,提供了逐行读取的便捷方法:
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class BufferedReaderDemo {
public static void main(String[] args) {
try (BufferedReader br = new BufferedReader(
new FileReader("D:/test/poem.txt"))) {
String line;
int lineNum = 1;
// readLine() 逐行读取,读到末尾返回null
while ((line = br.readLine()) != null) {
System.out.println((lineNum++) + ": " + line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}readLine()方法是BufferedReader独有的,其父类Reader中没有此方法,这是缓冲流提供的重要增强。
3.2 BufferedWriter
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
public class BufferedWriterDemo {
public static void main(String[] args) {
try (BufferedWriter bw = new BufferedWriter(
new FileWriter("D:/test/student_list.txt"))) {
bw.write("学号\t姓名\t成绩");
bw.newLine(); // 写入系统相关的换行符(跨平台兼容)
bw.write("001\t张三\t95");
bw.newLine();
bw.write("002\t李四\t88");
bw.newLine();
bw.write("003\t王五\t92");
System.out.println("成绩单保存成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
}bw.newLine()是跨平台的换行方法,会根据操作系统自动选择\r\n(Windows)或\n(Linux/Mac)。
四、转换流:InputStreamReader
InputStreamReader是字节流到字符流的桥梁,它将字节输入流按照指定的字符编码转换为字符输入流:
import java.io.*;
public class InputStreamReaderDemo {
public static void main(String[] args) {
// 读取GBK编码的文件(Windows中文系统默认编码)
try (InputStreamReader isr = new InputStreamReader(
new FileInputStream("D:/test/gbk_file.txt"), "GBK");
BufferedReader br = new BufferedReader(isr)) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
// 对比:FileReader使用系统默认编码,可能产生乱码
try (BufferedReader br = new BufferedReader(
new FileReader("D:/test/gbk_file.txt"))) {
// 如果文件是GBK编码,而系统默认是UTF-8,此处就会乱码
String line = br.readLine();
System.out.println("FileReader读取:" + line);
} catch (IOException e) {
e.printStackTrace();
}
}
}关键理解:
FileReader=new InputStreamReader(new FileInputStream(...))(使用系统默认编码)- 当需要指定非默认编码时,必须使用
InputStreamReader
五、转换流:OutputStreamWriter
OutputStreamWriter是字符流到字节流的桥梁:
import java.io.*;
public class OutputStreamWriterDemo {
public static void main(String[] args) {
String content = "这是一段中文文本,包含特殊字符 © ® ™";
// 写入为UTF-8编码文件
try (OutputStreamWriter osw = new OutputStreamWriter(
new FileOutputStream("D:/test/utf8_output.txt"), "UTF-8");
BufferedWriter bw = new BufferedWriter(osw)) {
bw.write(content);
System.out.println("UTF-8文件写入成功");
} catch (IOException e) {
e.printStackTrace();
}
// 写入为GBK编码文件(相同内容,不同编码)
try (OutputStreamWriter osw = new OutputStreamWriter(
new FileOutputStream("D:/test/gbk_output.txt"), "GBK");
BufferedWriter bw = new BufferedWriter(osw)) {
bw.write(content);
System.out.println("GBK文件写入成功");
} catch (IOException e) {
e.printStackTrace();
}
}
}六、流的装饰组合
Java IO流的核心设计模式是装饰器模式,可以通过层层嵌套组合功能:
import java.io.*;
public class StreamDecoratorDemo {
public static void main(String[] args) throws IOException {
// 经典三层装饰组合
// FileInputStream(底层) → InputStreamReader(解码) → BufferedReader(缓冲+按行读)
try (BufferedReader br = new BufferedReader(
new InputStreamReader(
new FileInputStream("D:/test/data.txt"), "UTF-8"))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
}
// 写入组合
// FileOutputStream(底层) → OutputStreamWriter(编码) → BufferedWriter(缓冲+换行)
try (BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream("D:/test/result.txt"), "UTF-8"))) {
bw.write("装饰器模式组合示例");
bw.newLine();
bw.write("灵活高效");
}
}
}装饰器链条使得每种流只负责单一职责:
FileInputStream:负责与文件交互InputStreamReader:负责字节到字符的转换和编码BufferedReader:负责缓冲和提供便利方法
七、性能对比实验
import java.io.*;
public class PerformanceTest {
static final String SRC = "D:/test/large.dat";
static final String DEST = "D:/test/copy.dat";
public static void main(String[] args) throws IOException {
// 1. 基础字节流(最慢)
testCopy("基础字节流", new FileInputStream(SRC), new FileOutputStream(DEST));
// 2. 缓冲字节流
testCopy("缓冲字节流",
new BufferedInputStream(new FileInputStream(SRC)),
new BufferedOutputStream(new FileOutputStream(DEST)));
// 3. 自定义缓冲区字节流
testCopy("自定义缓冲区(4KB)",
new FileInputStream(SRC), new FileOutputStream(DEST), 4096);
}
static void testCopy(String name, InputStream in, OutputStream out)
throws IOException {
long start = System.currentTimeMillis();
try (in; out) {
byte[] buf = new byte[1024];
while (in.read(buf) != -1) {
out.write(buf);
}
}
long end = System.currentTimeMillis();
System.out.println(name + " 耗时:" + (end - start) + "ms");
}
static void testCopy(String name, InputStream in, OutputStream out, int bufSize)
throws IOException {
long start = System.currentTimeMillis();
try (in; out) {
byte[] buf = new byte[bufSize];
int len;
while ((len = in.read(buf)) != -1) {
out.write(buf, 0, len);
}
}
long end = System.currentTimeMillis();
System.out.println(name + " 耗时:" + (end - start) + "ms");
}
}总结
本文深入讲解了Java IO中的缓冲流与转换流:
- 缓冲流(BufferedInputStream / BufferedOutputStream / BufferedReader / BufferedWriter):通过内置缓冲区减少磁盘IO次数,显著提升读写性能。BufferedReader的
readLine()和BufferedWriter的newLine()是常用的便捷方法 - 转换流(InputStreamReader / OutputStreamWriter):在字节流和字符流之间建立桥梁,支持指定字符编码,解决跨编码场景下的乱码问题
- 装饰器模式:Java IO通过层层包装组合不同流的功能,实现了高度的灵活性和可扩展性
在实际项目中,推荐始终使用缓冲流包裹基础流,并根据需要添加转换流来指定编码。
✅ 亮点总结
- 缓冲流内部默认8KB缓冲区的工作机制:一次磁盘IO填充缓冲区,后续read()直接从内存读取,大幅减少系统调用
- BufferedReader/BufferedWriter提供的readLine()和newLine()便捷方法,newLine()自动适配操作系统换行符
- InputStreamReader/OutputStreamWriter作为字节-字符桥梁,核心价值在于允许显式指定字符编码(GBK/UTF-8)
- FileReader本质是
new InputStreamReader(new FileInputStream(...), 默认编码),指定编码时需显式使用转换流 - 装饰器模式三层经典组合:底层FileStream(文件交互)→转换流(编码处理)→缓冲流(性能优化+便利方法)
适用场景
- 大文件日志的逐行分析与数据统计,利用BufferedReader逐行读取避免一次性加载
- 跨编码系统的数据迁移,如GBK编码的遗留系统数据转换为UTF-8存入新系统
- 高效文件读写工具类的封装,统一对外提供按行读取、批量写入等便捷接口
扩展方向
- 深入学习Java NIO的Channel与Buffer(非阻塞IO),理解BIO到NIO的架构升级
- 研究Java 7 NIO.2的Files工具类(Path、Files.copy/walk等现代化文件操作API)
到此这篇关于Java缓冲流与转换流全面解析的文章就介绍到这了,更多相关Java缓冲流与转换流内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
