字节流与字符流的转换
字节流和字符流之间的转换是 Java IO 中非常重要的一块。它解决的问题是:底层数据以字节形式存在,但业务代码希望按字符处理文本。
# 1. 转换流是什么
Java 提供了两个转换流:
InputStreamReader:把字节输入流转换为字符输入流。OutputStreamWriter:把字符输出流转换为字节输出流。
InputStream -> InputStreamReader -> Reader
OutputStream <- OutputStreamWriter <- Writer
转换时必须关注字符集。
# 2. InputStreamReader
InputStreamReader 用于把字节解码成字符。
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
public class InputStreamReaderDemo {
public static void main(String[] args) throws IOException {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(new FileInputStream("demo.txt"), StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
}
}
这里的数据处理过程是:
文件字节 -> FileInputStream -> InputStreamReader按UTF-8解码 -> BufferedReader按行读取
# 3. OutputStreamWriter
OutputStreamWriter 用于把字符编码成字节。
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
public class OutputStreamWriterDemo {
public static void main(String[] args) throws IOException {
try (BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream("demo.txt"), StandardCharsets.UTF_8))) {
writer.write("Java IO 转换流");
writer.newLine();
}
}
}
数据处理过程是:
Java字符 -> OutputStreamWriter按UTF-8编码 -> FileOutputStream -> 文件字节
# 4. 为什么一定要指定字符集
乱码的根本原因通常是编码和解码使用的字符集不一致。
写入时:字符 -> UTF-8字节
读取时:GBK字节 -> 字符
如果写入和读取的字符集不一致,程序仍然能读到数据,但读出来的字符可能是错误的。
推荐写法:
new InputStreamReader(inputStream, StandardCharsets.UTF_8);
new OutputStreamWriter(outputStream, StandardCharsets.UTF_8);
不推荐依赖默认字符集:
new InputStreamReader(inputStream);
new OutputStreamWriter(outputStream);
# 5. 常见应用场景
# 5.1 读取 HTTP 请求体
Web 容器底层接收到的是字节,但业务代码需要文本 JSON:
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8))) {
String body = reader.lines().collect(java.util.stream.Collectors.joining("\n"));
}
# 5.2 处理指定编码文件
老系统经常有 GBK 文件:
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(new FileInputStream("legacy.txt"), Charset.forName("GBK")))) {
// process text
}
# 5.3 网络协议文本解析
Socket 中传输的是字节,如果协议内容是文本,就需要转换流。
# 6. 与 Files API 的关系
现代 Java 代码中,更推荐直接使用 NIO.2 提供的封装:
Files.newBufferedReader(path, StandardCharsets.UTF_8);
Files.newBufferedWriter(path, StandardCharsets.UTF_8);
这些方法内部已经完成了字节流、字符集、缓冲能力的组合,代码更简洁。
# 专家实践清单
| 场景 | 组合方式 | 注意点 |
|---|---|---|
| 字节输入转文本 | InputStreamReader | 指定源数据编码 |
| 文本输出转字节 | OutputStreamWriter | 指定目标文件编码 |
| 网络协议文本部分 | 字节流 + 显式解码 | 先识别消息边界 |
| 文件编码转换 | Reader + Writer | 不要用默认编码中转 |
转换流的定位:
InputStream
│ byte
▼
InputStreamReader
│ char
▼
Reader / BufferedReader
Writer / BufferedWriter
│ char
▼
OutputStreamWriter
│ byte
▼
OutputStream
# Tips 快问快答
Q:转换流解决什么问题? A:解决字节流和字符流之间的编码/解码转换问题。
Q:InputStreamReader 做什么?
A:把字节输入流按指定字符集解码成字符输入流。
Q:OutputStreamWriter 做什么?
A:把字符输出按指定字符集编码成字节输出。
Q:为什么构造转换流时必须传 Charset? A:避免使用平台默认编码导致跨环境乱码。
Q:转换流能不能修复已经乱码的文本? A:通常不能,只能在源字节还没有被错误解码前正确处理。
Q:网络文本协议一定要用字符流吗? A:不一定。很多协议要先按字节识别边界,再对文本字段解码。
Q:Files.newBufferedReader 和转换流是什么关系?
A:它内部已经封装了字节到字符的转换和缓冲能力。
Q:最常见的转换流错误是什么? A:读写两端使用不同编码,或者中间多次错误转码。
# 7. 总结
转换流是字节流和字符流之间的桥梁。只要底层数据是字节,而业务语义是文本,就需要考虑转换流。生产代码中最重要的实践是显式指定字符集,并保证写入和读取使用一致的编码规则。