InputStreamReader 與 OutputStreamWriter 是 Java IO 體系中連接位元組流(InputStream/OutputStream)與字元流(Reader/Writer)的關鍵轉換器。它們內部委派給 StreamDecoder 與 StreamEncoder(基於 NIO Charset),負責位元組序列與字元序列的雙向編解碼,支援指定任意字元集(UTF-8、GBK、Big5 等),是處理國際化文字、網路協定、檔案編碼的基石。
// 使用平台預設編碼
InputStreamReader(InputStream in)
// 指定編碼名稱(如 "UTF-8", "GBK", "Big5")
InputStreamReader(InputStream in, String charsetName) throws UnsupportedEncodingException
// 指定 Charset 物件(推薦,型別安全)
InputStreamReader(InputStream in, Charset cs)
// 指定 CharsetDecoder(進階控制解碼行為)
InputStreamReader(InputStream in, CharsetDecoder dec)
String getEncoding() // 回傳實際使用的編碼名稱
int read() // 讀單字元
int read(char[] cbuf, int off, int len) // 批次讀入字元陣列
boolean ready() // 是否可立即讀取
void close() // 關閉並釋放底層流
// 使用平台預設編碼
OutputStreamWriter(OutputStream out)
// 指定編碼名稱
OutputStreamWriter(OutputStream out, String charsetName) throws UnsupportedEncodingException
// 指定 Charset 物件(推薦)
OutputStreamWriter(OutputStream out, Charset cs)
// 指定 CharsetEncoder(進階控制編碼行為)
OutputStreamWriter(OutputStream out, CharsetEncoder enc)
String getEncoding() // 回傳實際使用的編碼名稱
void write(int c) / write(char[] cbuf, int off, int len)
void write(String str) / write(String str, int off, int len)
void flush() // 刷新編碼器緩衝區至底層流
void close() // 關閉並釋放底層流
public class InputStreamReader extends Reader {
private final StreamDecoder sd; // 核心解碼器
public InputStreamReader(InputStream in) {
super(in);
try {
sd = StreamDecoder.forInputStreamReader(in, this, (String)null);
} catch (UnsupportedEncodingException e) {
throw new Error(e); // 預設編碼理論上必存在
}
}
public int read(char[] cbuf, int offset, int length) throws IOException {
return sd.read(cbuf, offset, length); // 委派給 StreamDecoder
}
public void close() throws IOException {
sd.close();
}
}
public class OutputStreamWriter extends Writer {
private final StreamEncoder se; // 核心編碼器
public OutputStreamWriter(OutputStream out, String charsetName)
throws UnsupportedEncodingException {
super(out);
se = StreamEncoder.forOutputStreamWriter(out, this, charsetName);
}
public void write(char[] cbuf, int off, int len) throws IOException {
se.write(cbuf, off, len); // 委派給 StreamEncoder
}
public void flush() throws IOException {
se.flush(); // 刷新編碼器內部緩衝
}
}
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
public class StreamConverterDemo {
public static void main(String[] args) {
Path path = Path.of("encoding_demo.txt");
String text = "字節流轉為字符流示例0123456789";
// 1. 以 UTF-8 寫入
writeWithEncoding(path, text, StandardCharsets.UTF_8);
printHex(path, "UTF-8");
// 2. 以 GBK 寫入
writeWithEncoding(path, text, StandardCharsets.ISO_8859_1); // 模擬 GBK 環境
printHex(path, "GBK(模擬)");
// 3. 讀取驗證
readWithEncoding(path, StandardCharsets.UTF_8, "UTF-8 讀取");
readWithEncoding(path, StandardCharsets.ISO_8859_1, "GBK(模擬) 讀取");
}
private static void writeWithEncoding(Path path, String text, java.nio.charset.Charset cs) {
try (OutputStreamWriter osw = new OutputStreamWriter(
new FileOutputStream(path.toFile()), cs)) {
osw.write(text);
osw.write('\n');
} catch (IOException e) { e.printStackTrace(); }
}
private static void readWithEncoding(Path path, java.nio.charset.Charset cs, String label) {
try (InputStreamReader isr = new InputStreamReader(
new FileInputStream(path.toFile()), cs)) {
char[] buf = new char[100];
int len = isr.read(buf);
System.out.println(label + ": " + new String(buf, 0, len));
} catch (IOException e) { e.printStackTrace(); }
}
private static void printHex(Path path, String label) {
try {
byte[] bytes = Files.readAllBytes(path);
StringBuilder sb = new StringBuilder(label + " Hex: ");
for (byte b : bytes) sb.append(String.format("%02X ", b));
System.out.println(sb);
} catch (IOException e) { e.printStackTrace(); }
}
}
// UTF-8: 中文字佔 3 位元組
UTF-8 Hex: E5 AD 97 E7 AF 80 E6 B5 81 E8 BD AC E7 82 BA E5 AD 97 E7 AC A6 E6 B5 81 E7 A4 BA E4 BE 8B 30 31 32 33 34 35 36 37 38 39 0A
// GBK/Big5: 中文字佔 2 位元組
GBK Hex: D7 D6 BD A1 C7 FA B7 A2 C8 B7 B5 E7 A3 B0 BE DF D6 CE C4 30 31 32 33 34 35 36 37 38 39 0A
StandardCharsets.UTF_8 等常數,避免平台預設編碼導致跨平台亂碼。
// 讀取
BufferedReader br = new BufferedReader(
new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8));
// 寫入
BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8));
CharsetDecoder/Encoder 時可設定 CodingErrorAction.REPLACE、IGNORE 或 REPORT。| 現象 | 可能原因 | 解決方案 |
|---|---|---|
| 中文顯示亂碼( 或 ?) | 讀取編碼與寫入編碼不一致 | 統一指定 UTF-8,檢查資料庫、網路、檔案全鏈路編碼 |
| 特定字元丟失或變 ? | 目標編碼不支援該字元(如 GBK 不支援繁體部份字) | 改用 UTF-8,或設定 Encoder 為 REPLACE 模式 |
| 檔案開頭有 BOM (EF BB BF) | UTF-8 with BOM | 讀取時用 StandardCharsets.UTF_8 可自動處理,或手動跳過 |
| 補字元(Emoji、生僻字)變亂碼 | 使用了 UTF-16/UTF-32 或舊版 Java 不支援 | 確保 Java 8+,使用 StandardCharsets.UTF_8(支援補字元) |
InputStreamReader 與 OutputStreamWriter 是 Java 文字處理的編碼心臟。深入理解其委派機制、編碼選擇與緩衡策略,能從根本上解決亂碼困擾,構建健壯的國際化應用系統。