CharArrayReader 是字元導向的記憶體輸入流,繼承自 Reader。它將現有的 char[] 包裝為可流式讀取的 Reader,支援標記/重置、跳過、逐字元或批次讀取。無內部資料複製(直接引用外部陣列),極輕量。適合單元測試模擬輸入、字串解析器輸入適配、記憶體資料流式處理等場景。
// 建構子:直接引用外部陣列,不複製
CharArrayReader(char[] buf) // 讀取整個陣列
CharArrayReader(char[] buf, int offset, int length) // 讀取陣列片段 [offset, offset+length)
// 核心讀取
int read() // 讀單字元,-1 表 EOF
int read(char[] cbuf, int off, int len) // 批次讀入字元陣列
long skip(long n) // 跳過 n 字元
// 標記與重置(支援 markSupported() == true)
void mark(int readAheadLimit) // 標記目前位置,readAheadLimit 在此無實際限制
void reset() // 重置至標記位置
// 狀態查詢
boolean ready() // 總是 true(記憶體無阻塞)
boolean markSupported() // 總是 true
void close() // 標記關閉,後續讀取拋 IOException
public class CharArrayReader extends Reader {
protected char[] buf; // 引用外部字元陣列
protected int pos; // 下一個讀取位置
protected int markedPos; // 標記位置
protected int count; // 有效資料結束位置(排他性上界)
public CharArrayReader(char[] buf) {
this.buf = buf;
this.pos = 0;
this.count = buf.length;
}
public CharArrayReader(char[] buf, int offset, int length) {
if (offset < 0 || offset > buf.length || length < 0 ||
(offset + length) < 0 || (offset + length) > buf.length) {
throw new IllegalArgumentException();
}
this.buf = buf;
this.pos = offset;
this.markedPos = offset;
this.count = Math.min(offset + length, buf.length);
}
// 讀取單字元
public int read() {
synchronized (lock) {
ensureOpen();
if (pos >= count) return -1;
return buf[pos++];
}
}
// 批次讀取
public int read(char[] cbuf, int off, int len) {
synchronized (lock) {
ensureOpen();
if (pos >= count) return -1;
int avail = count - pos;
if (len > avail) len = avail;
if (len <= 0) return 0;
System.arraycopy(buf, pos, cbuf, off, len);
pos += len;
return len;
}
}
// 標記:記錄目前 pos
public void mark(int readAheadLimit) {
markedPos = pos; // 記憶體流無長度限制,參數僅為介面相容
}
// 重置:回到標記位置
public void reset() {
synchronized (lock) {
ensureOpen();
pos = markedPos;
}
}
// 跳過
public long skip(long n) {
synchronized (lock) {
ensureOpen();
if (pos + n > count) n = count - pos;
if (n < 0) return 0;
pos += n;
return n;
}
}
private void ensureOpen() throws IOException {
if (buf == null) throw new IOException("Stream closed");
}
}
import java.io.CharArrayReader;
import java.io.IOException;
import java.io.LineNumberReader;
import java.io.PushbackReader;
public class CharArrayReaderDemo {
public static void main(String[] args) {
demoBasicParsing();
demoMarkReset();
demoTestSimulation();
demoPushbackParsing();
}
// 基本解析:模擬 CSV 行解析
private static void demoBasicParsing() {
String csvLine = "1001,筆記型電腦,35000,庫存充足";
CharArrayReader car = new CharArrayReader(csvLine.toCharArray());
StringBuilder field = new StringBuilder();
int c;
try {
while ((c = car.read()) != -1) {
char ch = (char) c;
if (ch == ',') {
System.out.println("欄位: " + field);
field.setLength(0);
} else {
field.append(ch);
}
}
System.out.println("最後欄位: " + field);
} catch (IOException e) { e.printStackTrace(); }
}
// 標記重置:向前看一字元決定解析邏輯
private static void demoMarkReset() {
String text = "abc123def";
CharArrayReader car = new CharArrayReader(text.toCharArray());
try {
// 讀 a b c
for (int i = 0; i < 3; i++) System.out.print((char) car.read());
System.out.println();
car.mark(0); // 標記位置 3 (字元 '1')
// 向前看 3 字元:1 2 3
char[] lookahead = new char[3];
car.read(lookahead, 0, 3);
System.out.println("向前看: " + new String(lookahead));
car.reset(); // 回到 '1'
// 正式讀取數字部分
char[] num = new char[3];
car.read(num, 0, 3);
System.out.println("解析數字: " + new String(num));
} catch (IOException e) { e.printStackTrace(); }
}
// 測試模擬:提供固定輸入給被測解析器
private static void demoTestSimulation() {
// 被測解析器:讀取整數直到非數字
class NumberParser {
public int parseInt(CharArrayReader in) throws IOException {
int value = 0;
int c;
while ((c = in.read()) != -1) {
char ch = (char) c;
if (ch >= '0' && ch <= '9') {
value = value * 10 + (ch - '0');
} else {
in.reset(); // 退回非數字字元
break;
}
}
return value;
}
}
String input = "42hello";
CharArrayReader car = new CharArrayReader(input.toCharArray());
car.mark(0); // 允許 parser reset
NumberParser parser = new NumberParser();
try {
int num = parser.parseInt(car);
System.out.println("解析整數: " + num); // 42
// 繼續讀取剩餘
char[] rest = new char[10];
int len = car.read(rest, 0, rest.length);
System.out.println("剩餘內容: " + new String(rest, 0, len)); // hello
} catch (IOException e) { e.printStackTrace(); }
}
// PushbackReader 組合:更強大的向前看/退回
private static void demoPushbackParsing() {
String code = "if (x > 0) { return x; }";
CharArrayReader car = new CharArrayReader(code.toCharArray());
PushbackReader pbr = new PushbackReader(car, 16); // 16 字元退回緩衝
try {
int c;
while ((c = pbr.read()) != -1) {
char ch = (char) c;
if (ch == 'i') { // 可能是 if
char[] keyword = new char[2];
pbr.read(keyword); // 讀 f (
if (new String(keyword).equals("f ")) {
System.out.println("發現關鍵字: if");
// 退回已讀的 "f " 以便後續處理
pbr.unread(keyword);
} else {
pbr.unread(keyword);
}
}
}
} catch (IOException e) { e.printStackTrace(); }
}
}
| 特性 | CharArrayReader | StringReader | ByteArrayInputStream |
|---|---|---|---|
| 資料來源 | char[] (可指定片段) | String | byte[] |
| 資料複製 | ❌ 直接引用 | ❌ 內部轉 char[] | ❌ 直接引用 |
| 編碼處理 | 無(已是字元) | 無 | 需指定編碼解碼 |
| 標記/重置 | ✅ 無限制 | ✅ 無限制 | ✅ 有限制 |
| 適用場景 | 字元陣列片段、高效能解析 | 完整字串、簡單模擬 | 位元組資料、編碼轉換測試 |
char[],避免 String.toCharArray() 複製,大資料解析效能最佳。offset, length 可只讀取陣列一部分,適合緩衝區切片處理。mark() 的 readAheadLimit 參數在記憶體流中無效果,可放心標記後大量讀取再 reset()。PushbackReader 更靈活。CharArrayReader 與 StringReader 驗證解析器對不同 Reader 實現的相容性。close() 僅將 buf = null,外部陣列引用不受影響,記憶體由外部管理。pos 為共享狀態,並發讀取會導致位置錯亂,需每執行緒建立獨立實例。int,-1 表 EOF,需轉 char 前檢查。CharArrayReader 是記憶體字元陣列流式讀取的輕量適配器,零複製、標記自由、片段支援等特性,使其成為高效能文字解析、測試模擬、介面適配的利器。