← [ BACK_TO_NOTES ]

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 必须是不同文件。

结合场景再看三个关注点

  1. 文本用 Reader/Writer + Charset。
  2. 缓冲是装饰能力,不是另一种数据模型。
  3. 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/Writer
  • Files.readString/writeString(小文件)

边界情况与反直觉行为

  1. JDK 18+ 默认字符集通常是 UTF-8,旧版本/配置可能不同;外部数据仍应按协议明确 Charset,而非猜测本机默认。\
  2. 以 byte 读文本易截断多字节字符。
  3. 大文件避免一次 readAllBytes;flush 通常只把应用缓冲交给底层,不等于持久落盘。需要持久化时另查 FileChannel.force/FileDescriptor.sync 等契约。\

常见误区

[!warning] 常见误区:二进制当字符流读 压缩包/图片用字节流,否则编码破坏数据。

工程实践

  1. 统一 UTF-8。
  2. 缓冲区大小按场景,不玄学调参。
  3. 网络 IO 设超时(更高层 API)。
  4. 优先 NIO.2 Path/Files 管理文件路径。

本节总结

  • 字节 vs 字符是第一刀
  • 装饰器组合能力
  • 编码显式 + 资源关闭

自测题

  1. 何时必须用 InputStream 而不是 Reader?
  2. BufferedReader 解决什么?
参考答案
  1. 非文本字节数据或需精确字节协议时。
  2. 按行高效读取与减少底层读取次数。

延伸阅读与资料来源

资料类型支撑内容
I/O Streams (Oracle Tutorial)教程流模型
java.io packageAPI类型入口
创建于 2026/7/15 更新于 2026/10/6
Digital Biome
Access

键盘导航

j / k / ↑ / ↓
选择对象
Enter / o
打开选择
/ 或 Ctrl+K
全站搜索
i / f / 1–4
筛选当前列表
g → h / l / p / t / a
首页 / 笔记 / 项目 / 工具 / About
r → 1–9
打开所选对象的关联
u
返回上一页
Esc
关闭浮层 / 取消命令;输入框内清空文本