開源專案 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:記憶體緩衝區,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();
}
}
方案一仍需重複 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、零重複解析、語義清晰、記憶體佔用固定。
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() | 預設 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) |
new ByteArrayInputStream("mock data".getBytes())InputStream 消費者介面,透明支援重複讀取toByteArray() 會複製陣列:頻繁呼叫產生 GC 壓力,方案二緩存 Document 更佳ByteArrayOutputStream 需同步InputStream.readAllBytes() 簡化讀取