首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java IO 記憶體流:CharArrayReader 字元陣列輸入流完整指南

     編輯:Java 相關知識

核心定位:記憶體字元陣列的流式讀取器

CharArrayReader 是字元導向的記憶體輸入流,繼承自 Reader。它將現有的 char[] 包裝為可流式讀取的 Reader,支援標記/重置、跳過、逐字元或批次讀取。無內部資料複製(直接引用外部陣列),極輕量。適合單元測試模擬輸入、字串解析器輸入適配、記憶體資料流式處理等場景。

建構子與核心 API


// 建構子:直接引用外部陣列,不複製
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 vs StringReader vs ByteArrayInputStream

特性CharArrayReaderStringReaderByteArrayInputStream
資料來源char[] (可指定片段)Stringbyte[]
資料複製❌ 直接引用❌ 內部轉 char[]❌ 直接引用
編碼處理無(已是字元)無需指定編碼解碼
標記/重置✅ 無限制✅ 無限制✅ 有限制
適用場景字元陣列片段、高效能解析完整字串、簡單模擬位元組資料、編碼轉換測試

最佳實踐建議

  1. 零複製優勢:直接包裝現有 char[],避免 String.toCharArray() 複製,大資料解析效能最佳。
  2. 片段讀取:建構子指定 offset, length 可只讀取陣列一部分,適合緩衝區切片處理。
  3. 標記無限制:mark() 的 readAheadLimit 參數在記憶體流中無效果,可放心標記後大量讀取再 reset()。
  4. 組合 PushbackReader:需要多字元退回(如詞法分析器)時,包裝 PushbackReader 更靈活。
  5. 測試雙重保險:單元測試同時提供 CharArrayReader 與 StringReader 驗證解析器對不同 Reader 實現的相容性。

常見誤區

  • 以為 close() 會釋放陣列:close() 僅將 buf = null,外部陣列引用不受影響,記憶體由外部管理。
  • 多執行緒共享同一 CharArrayReader:pos 為共享狀態,並發讀取會導致位置錯亂,需每執行緒建立獨立實例。
  • read() 回傳 char 而非 int:回傳 int,-1 表 EOF,需轉 char 前檢查。

CharArrayReader 是記憶體字元陣列流式讀取的輕量適配器,零複製、標記自由、片段支援等特性,使其成為高效能文字解析、測試模擬、介面適配的利器。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved