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

Java ByteArrayOutputStream/InputStream 避免重複讀取配置檔優化實戰

     編輯:Java 相關知識

問題背景:Mycat 專案配置檔重複讀取

開源專案 Mycat 中,ConfigFactory.loadRoot() 被多個業務方法頻繁呼叫,每次都重新開啟、讀取、關閉 mycat.dtd 與 mycat.xml,造成嚴重效能浪費。

原始低效代碼

private static Element loadRoot() {
    InputStream dtd = ConfigFactory.class.getResourceAsStream("/mycat.dtd");
    InputStream xml = ConfigFactory.class.getResourceAsStream("/mycat.xml");
    Element root = ConfigUtil.getDocument(dtd, xml).getDocumentElement();
    // finally 關閉流...
    return root;
}

// 多處呼叫
public UserConfig getUserConfig(String user) {
    Element root = loadRoot(); // 每次重讀檔案
    return loadUsers(root).get(user);
}
public SystemConfig getSystemConfig() {
    Element root = loadRoot(); // 每次重讀檔案
    return loadSystem(root);
}

優化方案一:ByteArrayOutputStream 緩存位元組陣列

核心原理

  • ByteArrayOutputStream:記憶體緩衝區,write() 累積資料,toByteArray() 取得 byte[]
  • ByteArrayInputStream:從 byte[] 建立唯讀 InputStream,可多次建立獨立實例
  • 同一 byte[] 可無限次產生新 InputStream,各自獨立 position 指標

實作代碼

public class LocalLoader implements ConfigLoader {
    // 靜態緩存區
    private static byte[] xmlBuffer, dtdBuffer;
    private static ByteArrayOutputStream xmlBaos, dtdBaos;
    
    // 類載入時一次性讀取緩存
    static {
        bufferFile(ConfigFactory.class.getResourceAsStream("/mycat.dtd"), dtdBuffer, dtdBaos);
        bufferFile(ConfigFactory.class.getResourceAsStream("/mycat.xml"), xmlBuffer, xmlBaos);
    }
    
    private static void bufferFile(InputStream in, byte[] buf, ByteArrayOutputStream baos) {
        if (in == null) return;
        try {
            buf = new byte[1024 * 512]; // 512KB 緩衝
            baos = new ByteArrayOutputStream();
            int len;
            while ((len = in.read(buf)) > -1) baos.write(buf, 0, len);
            baos.flush();
        } catch (IOException e) { /* log */ }
    }
    
    // 業務方法:從緩存重建 InputStream
    private static Element loadRoot() {
        InputStream xml = xmlBaos != null ? new ByteArrayInputStream(xmlBaos.toByteArray()) : null;
        InputStream dtd = dtdBaos != null ? new ByteArrayInputStream(dtdBaos.toByteArray()) : null;
        return ConfigUtil.getDocument(dtd, xml).getDocumentElement();
    }
}

優化方案二:直接緩存 Document 物件 (推薦)

方案一仍需重複 toByteArray()、建立流、解析 DOM。若配置檔不變,直接緩存解析後的 Document 最佳。

public class LocalLoader implements ConfigLoader {
    private static Document document; // 緩存 DOM 樹
    
    private static Element loadRoot() {
        if (document == null) {
            InputStream dtd = getResource("/mycat.dtd");
            InputStream xml = getResource("/mycat.xml");
            document = ConfigUtil.getDocument(dtd, xml); // 僅解析一次
        }
        return document.getDocumentElement();
    }
}

優勢:零重複 I/O、零重複解析、語義清晰、記憶體佔用固定。

通用工具類:Stream 緩存輔助

public final class StreamCacheUtil {
    // 將 InputStream 完整讀入 byte[]
    public static byte[] readAllBytes(InputStream in) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buf = new byte[8192];
        int len;
        while ((len = in.read(buf)) != -1) baos.write(buf, 0, len);
        return baos.toByteArray();
    }
    
    // 從 byte[] 建立新 InputStream
    public static InputStream newInputStream(byte[] data) {
        return new ByteArrayInputStream(data);
    }
    
    // 典型用法:網路回應同時日誌與存檔
    public static void demo(HttpURLConnection conn) throws IOException {
        byte[] data = readAllBytes(conn.getInputStream());
        try (InputStream in1 = newInputStream(data);
             InputStream in2 = newInputStream(data)) {
            logResponse(in1);      // 第一次讀取
            saveToFile(in2);       // 第二次讀取,獨立 position
        }
    }
}

ByteArrayOutputStream / InputStream API 速查

方法說明
ByteArrayOutputStream()預設 32 bytes 緩衝區
ByteArrayOutputStream(int size)指定初始緩衝區大小
write(byte[] b, int off, int len)寫入資料
toByteArray()取得內容副本 (新陣列)
toString() / toString(charset)直接轉字串
reset()重置緩衝區 (保留陣列供復用)
ByteArrayInputStream(byte[] buf)包裝整個陣列
ByteArrayInputStream(byte[], offset, length)包裝陣列片段
read() / read(byte[])讀取資料,獨立 position
reset()position 歸零 (支援 mark/reset)

適用場景總結

  1. 單次讀取、多次消費:HTTP 回應、檔案上傳、配置檔
  2. 單元測試模擬輸入:new ByteArrayInputStream("mock data".getBytes())
  3. 記憶體管線處理:序列化 → 壓縮 → 加密 → 輸出
  4. 舊系統改造:不修改 InputStream 消費者介面,透明支援重複讀取

注意事項

  • 記憶體壓力:大檔案慎用,考慮臨時檔或分塊處理
  • toByteArray() 會複製陣列:頻繁呼叫產生 GC 壓力,方案二緩存 Document 更佳
  • 非執行緒安全:多執行緒共用 ByteArrayOutputStream 需同步
  • Java 9+ 可用 InputStream.readAllBytes() 簡化讀取
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved