Java IO 流与 Reader
字节流与字符流的职责分层、装饰器组合,以及读写时的编码与缓冲实践。
#concept
#growing
#dev
#java
[!info] related notes
Java IO 流与 Reader
这个概念为什么出现
文件、网络、进程管道最终都是字节;人类文本还涉及字符编码。Java 经典 IO 分成字节流(InputStream/OutputStream)与字符流(Reader/Writer),并用装饰器叠加缓冲、转换等能力。
[!abstract] 一句话理解 字节流处理原始字节;字符流在 Reader/Writer 层处理文本;用 InputStreamReader 等桥接并显式指定 Charset,缓冲装饰器减少系统调用。
最小可运行示例
场景:读取 UTF-8 配置的第一行并写到另一文件
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
public class ConfigLineCopy {
static void copyFirstLine(Path src, Path dst) throws IOException {
try (BufferedReader r = Files.newBufferedReader(src, StandardCharsets.UTF_8);
BufferedWriter w = Files.newBufferedWriter(dst, StandardCharsets.UTF_8)) {
String line = r.readLine();
if (line != null) {
w.write(line);
w.newLine();
}
}
}
public static void main(String[] args) throws Exception {
Path src = Files.createTempFile("cfg-", ".txt");
try {
Path dst = Files.createTempFile("cfg-bak-", ".txt");
try {
Files.writeString(src, "mode=prod\n", StandardCharsets.UTF_8);
copyFirstLine(src, dst);
System.out.println(Files.readString(dst, StandardCharsets.UTF_8).trim());
} finally {
Files.deleteIfExists(dst);
}
} finally {
Files.deleteIfExists(src);
}
}
}
建议运行:
javac ConfigLineCopy.java && java ConfigLineCopy
期望输出:
mode=prod
本例只复制第一行:readLine 去掉行终止符,newLine 使用系统行分隔符,不是字节级备份。默认打开目标 Writer 会截断已有文件,src/dst 必须是不同文件。
结合场景再看三个关注点
- 文本用 Reader/Writer + Charset。
- 缓冲是装饰能力,不是另一种数据模型。
- TWR 在离开作用域时尝试 close;本例 BufferedWriter.close 会 flush,但可能抛异常。不能把所有 OutputStream.close 都当作自动刷新或持久化保证。
核心概念与准确模型
1. 字节流
- 无参 InputStream.read() 返回 0–255 的字节值,或 -1 表示 EOF;read(byte[]) 返回读取数量,可能短读,不能把整个缓冲区都当作有效数据。\
- 适合图片、protobuf、加密 blob
2. 字符流
- Reader 以 UTF-16 char 代码单元为单位;一个 Unicode 码点可能占两个 char,不能把一次 read() 当作一个完整字符。\
InputStreamReader负责字节→字符解码
3. 装饰器组合
FileInputStream → BufferedInputStream → ...
InputStreamReader → BufferedReader
4. 现代便捷入口
Files.newBufferedReader/WriterFiles.readString/writeString(小文件)
边界情况与反直觉行为
- JDK 18+ 默认字符集通常是 UTF-8,旧版本/配置可能不同;外部数据仍应按协议明确 Charset,而非猜测本机默认。\
- 以 byte 读文本易截断多字节字符。
- 大文件避免一次 readAllBytes;flush 通常只把应用缓冲交给底层,不等于持久落盘。需要持久化时另查 FileChannel.force/FileDescriptor.sync 等契约。\
常见误区
[!warning] 常见误区:二进制当字符流读 压缩包/图片用字节流,否则编码破坏数据。
工程实践
- 统一 UTF-8。
- 缓冲区大小按场景,不玄学调参。
- 网络 IO 设超时(更高层 API)。
- 优先 NIO.2 Path/Files 管理文件路径。
本节总结
- 字节 vs 字符是第一刀
- 装饰器组合能力
- 编码显式 + 资源关闭
自测题
- 何时必须用 InputStream 而不是 Reader?
- BufferedReader 解决什么?
参考答案
- 非文本字节数据或需精确字节协议时。
- 按行高效读取与减少底层读取次数。
延伸阅读与资料来源
| 资料 | 类型 | 支撑内容 |
|---|---|---|
| I/O Streams (Oracle Tutorial) | 教程 | 流模型 |
| java.io package | API | 类型入口 |