Skip to content

转换流:字节到字符的桥梁

InputStreamReaderOutputStreamWriter 是 Java IO 中最容易被人忽视的类——因为它们的名字太像「工具类」而不是「主角」。

但实际上,它们是整个字符流体系的基础

FileReader 内部就是 InputStreamReaderFileWriter 内部就是 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 为什么不值得用

FileReaderFileWriter 本质上就是转换流的便捷包装:

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

结论FileReaderFileWriter 是历史遗留的「便捷类」,但它们的便捷是有代价的——无法指定编码。在生产环境中,这个代价不值得。

常见错误

编码不一致

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

              这里指定编码!

转换流不是配角,它是字符流的基石。

基于 VitePress 构建