字節流與字符流是 Java I/O 體系的雙支柱,前者處理二進制數據,後者處理文本數據。本文從底層原理、編碼轉換、緩衝機制、API 設計四個維度深度對比,給出場景化選型指南。
| 維度 | 字節流 | 字符流 |
|---|---|---|
| 基本單元 | 字節 (byte, 8 bit) | 字符 (char, UTF-16 碼元, 16 bit) |
| 抽象基類 | InputStream / OutputStream | Reader / Writer |
| 處理數據類型 | 二進制:圖片、視頻、音頻、序列化對象、網絡數據包 | 文本:配置文件、源碼、XML/JSON/CSV、日誌 |
| 編碼轉換 | 無 (原始字節透傳) | 核心職責:字符 ↔ 字節 (指定/默認 Charset) |
| 緩衝機制 | 默認無緩衝,需 BufferedInputStream 包裝 | 內建緩衝區 (轉碼器需要) |
| 典型實現類 | FileInputStream, ByteArrayInputStream, Socket.getInputStream() | FileReader, StringReader, InputStreamReader |
寫入流程 (Writer):
字符序列 (char[]/String)
→ StreamEncoder (CharsetEncoder)
→ 字節緩衝區 (byte[])
→ 底層 OutputStream
→ 文件/網絡
讀取流程 (Reader):
文件/網絡
→ 底層 InputStream
→ 字節緩衝區 (byte[])
→ StreamDecoder (CharsetDecoder)
→ 字符序列 (char[])
try (FileWriter fw = new FileWriter("demo.txt")) {
fw.write("demo"); // 使用系統默認編碼 (如 UTF-8)
}
// WinHex 查看 demo.txt: 64 65 6D 6F (UTF-8 編碼)
關鍵點:未指定編碼時使用 Charset.defaultCharset(),**跨平台必須顯式指定編碼**。
// InputStream.read(byte[]) 源碼實際循環調用 read()
public int read(byte[] b, int off, int len) {
for (int i = 0; i < len; i++) {
int c = read(); // 每次調用 → 系統調用 → 磁盤 I/O
if (c == -1) break;
b[off + i] = (byte) c;
}
return i;
}
解決:必須包裝 BufferedInputStream/BufferedOutputStream (默認 8KB 緩衝區)。
StreamEncoder/Decoder 內部維護字節緩衝區,轉碼批次完成後一次性寫入/讀出,效能遠優於無緩衝字節流。但大量寫入仍建議額外包裝 BufferedWriter。
// 字節流 → 字符流 (指定編碼)
Reader reader = new InputStreamReader(new FileInputStream("data.txt"), StandardCharsets.UTF_8);
Writer writer = new OutputStreamWriter(new FileOutputStream("out.txt"), StandardCharsets.UTF_8);
// 標準輸入輸出轉換
BufferedReader br = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8));
PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out, StandardCharsets.UTF_8), true);
這兩個類是**字節流與字符流的適配器**,承擔編碼/解碼核心職責。
| 操作 | 字節流 | 字符流 |
|---|---|---|
| 讀取單元 | int read() 返回 0-255 或 -1 | int read() 返回 Unicode 碼點或 -1 |
| 批量讀取 | read(byte[]) | read(char[]) / read(CharBuffer) |
| 讀取行 | 無 (需自行解析 \n) | BufferedReader.readLine() |
| 寫入 | write(int b) / write(byte[]) | write(int c) / write(char[]) / write(String) |
處理數據類型?
├─ 二進制 (圖片/視頻/序列化/網絡包) → 字節流
│ ├─ 需高效讀寫 → BufferedInputStream/OutputStream
│ └─ 對象序列化 → ObjectInputStream/OutputStream
└─ 文本 (配置/源碼/日誌/JSON)
├─ 簡單文件讀寫 → FileReader/FileWriter (指定編碼)
├─ 高效行讀取 → BufferedReader
├─ 格式化輸出 → PrintWriter
└─ 字節流轉字符流 → InputStreamReader/OutputStreamWriter
InputStreamReader/OutputStreamWriter 顯式指定
// Files API 簡潔高效,內部自動管理資源
String content = Files.readString(Path.of("file.txt"), StandardCharsets.UTF_8);
Files.writeString(Path.of("out.txt"), content, StandardCharsets.UTF_8);
List lines = Files.readAllLines(Path.of("file.txt"), StandardCharsets.UTF_8);
// 大文件流式處理
try (Stream lines = Files.lines(Path.of("big.log"), StandardCharsets.UTF_8)) {
lines.filter(s -> s.contains("ERROR")).forEach(System.out::println);
}
推薦:新項目優先使用 java.nio.file.Files,簡潔、安全、效能優化。