當現有解析器無法滿足性能、內存、功能需求時,自研高性能解析器是關鍵選項。本文介紹受 VTD-XML 啟發的「索引疊加」架構,通過 JSON 解析器實戰展示設計細節、基準測試與優化策略。
| 維度 | 順序訪問 (SAX/StAX) | 隨機訪問 (DOM/索引疊加) |
|---|---|---|
| 訪問模式 | 單向流式,不可回溯 | 任意跳轉,多次遍歷 |
| 內存佔用 | 極低(常數級) | 需載入全量數據 |
| 適用場景 | 大文件、流式處理、單遍歷 | 多輪分析、XPath/查詢、隨機提取 |
不構建對象樹,而是在原始數據緩衝區上構建索引層:
原始數據緩衝區 (byte[]/char[]) ← 唯一數據副本
↑
索引緩衝區 (int[] position, int[] length, byte[] type) ← 輕量元素描述符
↑
元素導航器 (語義級 API) ← 開發者交互介面
next(), asString(), countArrayElements())
void parseToken() {
skipWhitespace();
tokenPos[tokenIdx] = dataPos;
switch (data[dataPos]) {
case '{': type[tokenIdx] = T_LEFT_BRACE; tokenLen[tokenIdx] = 1; break;
case '}': type[tokenIdx] = T_RIGHT_BRACE; tokenLen[tokenIdx] = 1; break;
case '"': parseStringToken(); break; // 處理轉義、Unicode
case '0'...'9': parseNumberToken(); break;
case 't': if (parseTrue()) type[tokenIdx] = T_BOOLEAN; break;
case 'f': if (parseFalse()) type[tokenIdx] = T_BOOLEAN; break;
case 'n': if (parseNull()) type[tokenIdx] = T_NULL; break;
// ...
}
tokenIdx++;
}
void parseObject() {
expect(T_LEFT_BRACE);
addElement(E_OBJ_START);
while (currentToken() != T_RIGHT_BRACE) {
expect(T_STRING); addElement(E_PROP_NAME);
expect(T_COLON);
switch (currentToken()) {
case T_STRING: parseStringValue(); break;
case T_NUMBER: addElement(E_PROP_VAL_NUM); break;
case T_LEFT_BRACE: parseObject(); break; // 遞歸嵌套對象
case T_LEFT_BRACKET: parseArray(); break;
}
if (currentToken() == T_COMMA) nextToken();
}
expect(T_RIGHT_BRACE);
addElement(E_OBJ_END);
}
// 快速字段名比較:無需創建 String 對象
if (navigator.isEqualUnencoded("userId")) { ... }
// 基本類型數組元素計數:預分配精確大小數組
int[] ids = new int[navigator.countPrimitiveArrayElements()];
for (int i = 0; i < ids.length; i++) {
ids[i] = navigator.asInt();
navigator.next();
}
// Rope 字符串視圖:零拷貝字符串操作
Rope rope = navigator.asRope(); // {char[] ref, offset, length}
| 文件大小 | 模式 | Gson 流式 | Gson 樹 | Gson 反射 | 索引疊加 (JsonParser) | 索引疊加 (JsonParser2) |
|---|---|---|---|---|---|---|
| 58B | 10M 次 | 1,240ms | 1,890ms | 3,560ms | 680ms | 420ms |
| 783B | 1M 次 | 2,180ms | 3,420ms | 6,750ms | 1,050ms | 780ms |
| 1854B | 1M 次 | 4,950ms | 7,800ms | 15,200ms | 2,300ms | 1,650ms |
| 維度 | 索引疊加 | 流式解析器 |
|---|---|---|
| 大文件支援 | 需分塊處理 (如日志單行獨立) | 原生支援流式 |
| 錯誤報告 | 雙解析器策略:快解析失敗 → 慢解析定位 | 天然精確 |
| 解碼成本 | 可延遲/增量解碼 | 通常邊讀邊解碼 |
| 適用場景 | 中小文件、高頻解析、隨機查詢、嵌入式 | 大文件、單遍歷、管道流 |
索引疊加架構通過「數據不動、索引走位」實現極致性能。核心洞見:解析器不應創建對象樹,而應創建數據地圖。此模式已在 VTD-XML、simdjson、Pikkr 等高性能解析器中驗證,值得在性能敏感場景採用。