转换流:字节到字符的桥梁
InputStreamReader 和 OutputStreamWriter 是 Java IO 中最容易被人忽视的类——因为它们的名字太像「工具类」而不是「主角」。
但实际上,它们是整个字符流体系的基础。
FileReader 内部就是 InputStreamReader,FileWriter 内部就是 OutputStreamWriter。理解了转换流,你就理解了字符流的本质。
为什么需要转换流
字节流:数据是一串字节
FileInputStream → [0xE4, 0xBD, 0xA0, 0xE5, 0xA5, 0xBD]
字符流:数据是一串字符
Reader → ['你', '好']
转换流的作用:
FileInputStream(字节)→ InputStreamReader → BufferedReader(字符)
(字节→字符,指定编码)
BufferedWriter → OutputStreamWriter → FileOutputStream(字节)
(字符→字节,指定编码)InputStreamReader:字节 → 字符
java
// 最完整的构造方式
InputStreamReader isr = new InputStreamReader(
InputStream in, // 底层字节流
Charset charset // 指定字符编码
);
// 常用变体
InputStreamReader isr = new InputStreamReader(
new FileInputStream("utf8.txt"), // 字节流
StandardCharsets.UTF_8 // UTF-8 编码
);
// 如果不指定编码,用系统默认编码(不推荐)
InputStreamReader isr = new InputStreamReader(
new FileInputStream("data.txt")); // 依赖系统编码完整读取文本文件
java
// 读取整个文件到 String
String content;
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(
new FileInputStream("data.txt"), StandardCharsets.UTF_8))) {
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line).append("\n");
}
content = sb.toString();
}
// JDK 11+ 更简单
String content = Files.readString(Path.of("data.txt"), StandardCharsets.UTF_8);OutputStreamWriter:字符 → 字节
java
// 最完整的构造方式
OutputStreamWriter osw = new OutputStreamWriter(
OutputStream out, // 底层字节流
Charset charset // 指定字符编码
);
// 常用变体
OutputStreamWriter osw = new OutputStreamWriter(
new FileOutputStream("data.txt"), // 字节流
StandardCharsets.UTF_8 // UTF-8 编码
);完整写入文本文件
java
// 写入字符串到文件
try (BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream("data.txt"), StandardCharsets.UTF_8))) {
writer.write("你好,Java!");
writer.newLine();
writer.write("第二行内容");
}
// JDK 11+ 更简单
Files.writeString(Path.of("data.txt"), "你好,Java!", StandardCharsets.UTF_8);FileReader/FileWriter 为什么不值得用
FileReader 和 FileWriter 本质上就是转换流的便捷包装:
java
// FileReader 内部实现(简化)
public class FileReader extends InputStreamReader {
public FileReader(String fileName) throws FileNotFoundException {
super(new FileInputStream(fileName)); // 用系统默认编码!
}
}
// FileReader 的问题:无法指定编码
FileReader fr = new FileReader("utf8.txt"); // 永远用系统编码!
// 正确做法:用 InputStreamReader
BufferedReader reader = new BufferedReader(
new InputStreamReader(
new FileInputStream("utf8.txt"), StandardCharsets.UTF_8));结论:FileReader 和 FileWriter 是历史遗留的「便捷类」,但它们的便捷是有代价的——无法指定编码。在生产环境中,这个代价不值得。
常见错误
编码不一致
java
// ❌ 写入用 UTF-8,读取用 GBK → 乱码
try (BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream("data.txt"), StandardCharsets.UTF_8))) {
writer.write("你好");
}
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(
new FileInputStream("data.txt"), Charset.forName("GBK")))) {
reader.readLine(); // 乱码!
}
// ✅ 写入和读取用同一个编码
try (BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream("data.txt"), StandardCharsets.UTF_8))) {
writer.write("你好");
}
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(
new FileInputStream("data.txt"), StandardCharsets.UTF_8))) {
reader.readLine(); // 正常
}混淆字节和字符
java
// ❌ OutputStream 是字节流,不能直接写字符
try (OutputStream out = new FileOutputStream("data.txt")) {
out.write("你好"); // 编译错误!write() 只能写 byte
out.write("你好".getBytes()); // 正确,但失去了字符流的便利性
}
// ✅ OutputStreamWriter 把字符流转成字节流
try (OutputStreamWriter writer = new OutputStreamWriter(
new FileOutputStream("data.txt"), StandardCharsets.UTF_8)) {
writer.write("你好"); // 直接写字符,内部自动转成 UTF-8 字节
}Charset 的获取方式
java
// 方式 1:StandardCharsets 常量(推荐)
Charset utf8 = StandardCharsets.UTF_8;
Charset gbk = StandardCharsets.ISO_8859_1;
// 方式 2:Charset.forName()
Charset utf8 = Charset.forName("UTF-8");
Charset gbk = Charset.forName("GBK");
// 方式 3:Charset.defaultCharset()
Charset systemDefault = Charset.defaultCharset(); // 系统默认编码
// 方式 4:名字忽略大小写
Charset.forName("utf-8"); // 等价于 StandardCharsets.UTF_8记住这张图:
字节流 ←→ 转换流 ←→ 字符流
FileInputStream InputStreamReader BufferedReader
FileOutputStream OutputStreamWriter BufferedWriter
↑
这里指定编码!转换流不是配角,它是字符流的基石。
