解決 PHP 處理 JSON 大檔案遇到 Allowed memory exhausted 問題

PHP 處理 JSON 大檔案,如果直接將整個檔案讀入記憶體,很容易遇到 Allowed memory exhausted 問題。除了解決網站伺服器遇到 Allowed memory exhausted 問題提到使用 jq 將大檔案切割成小檔案的方法、或依照檔案內容結構,逐行使用 PHP Generator (產生器) 處理。也可以使用 JSON Streaming Parser 直接處理。

Purple PHP Women elephpant
Purple PHP Women elephpant by Philip Sharp 使用創用 CC by-sa 授權

問題狀況

JSON 檔案約 250 MB,使用 json_decode 函數處理,遭遇記憶體耗盡的錯誤訊息

PHP Fatal error:  Allowed memory size of 134217728 bytes exhausted (tried to allocate 20480 bytes)

問題解決

方法 1: 使用 jq - 處理 JSON 檔案的命令列工具

步驟 1: 安裝 jq

前往 jq 網站,根據作業系統版本,安裝對應的 jq。支援的作業系統有 Windows, Linux 與 OSX。根據個人經驗,Windows 版本 jq 建議透過 Cygwin 安裝 jq package。

步驟 2: 瞭解 JSON 內容結構

檔案太大時無法用一般文字編輯器打開。可以透過指令看部分檔案內容,例如逐頁看檔案內容

cat 檔名 | more
cat example.json | more  

按 space 按鍵換頁,再按 q 就可以離開逐頁觀看模式


步驟 3: 依照 JSON 內容結構,決定處理方式

格式 I 完整的 JSON 格式

例如:projects 下有上千多筆的專案資料,example.json 檔案內容像:

{
"projects": [
   {
      "id": 1,
      "name": "編號1專案"
   },
   {
      "id": 2,
      "name": "編號2專案"
   }
]
}

請前往 解決網站伺服器遇到 Allowed memory exhausted 問題,了解將大檔案切割成小檔案的方法

步驟 3-1: 計算記錄筆數

jq '結構樹 | length' 檔名
jq '.projects | length' example.json

步驟 3-2: 預覽第一筆記錄

第一筆從 0 開始,結構樹語法需要標記 [0

jq '結構樹' 檔名
jq '.projects[0]' example.json

範例檔案的結果是

{
"id": 1,
"name": "編號1產品"
}

步驟 3-3: 逐筆拆解紀錄,另存檔案

jq '結構樹' 檔名 > 輸出檔名
jq '.projects[0]' example.json > 0.json
jq '. projects[1]' example.json > 1.json
jq '. projects[2]' example.json > 2.json

因為檔案變小,後續就很容易解析小檔案內容。


格式 II 每行一筆 JSON 格式

例如:每行以 { 符號開始、} 符號結束。檔案內容看起來像:

{"id":1, "name":"編號1專案"}

{"id":2, "name":"編號2專案"}

步驟 3-1: 計算記錄筆數

wc -l 檔名
wc -l example.json

步驟 3-2: 預覽第一筆記錄

head -n 1 檔名 | jq .
head -n 1 example.json | jq .

上方指令先使用 head 指令,輸出第一行的檔案內容。再使用 | 符號 (pipe),當作 jq 的輸入資料,顯示比較好閱讀的 JSON 結構

使用 PHP Generator (產生器) 逐行讀取檔案內容即可。


方法 2: 使用 JSON Streaming Parser

GitHub 可以找到許多 JSON Streaming Parser (JSON 串流解析器) 套件,以下以 salsify/jsonstreamingparser 套件為例

步驟 1: 安裝 sergiorodenas/stream-parser 套件

composer require rodenastyle/stream-parser

步驟 2: 解析 JSON 內容

require_once __DIR__ . '/vendor/autoload.php';

use Rodenastyle\StreamParser\StreamParser;
use Tightenco\Collect\Support\Collection;

StreamParser::json($file_path)->each(function(Collection $row){

   var_dump($row, true);

});


參考資料

JSON Streaming Parser

  1. sergiorodenas/stream-parser: ⚡ PHP7 / Laravel Multi-format Streaming Parser
  2. pcrov/JsonReader: A JSON pull parser for PHP
  3. salsify/jsonstreamingparser: A JSON streaming parser implementation in PHP

無效的嘗試

% jq  --compact-output . test.json

並不會將 test.json 每個元素緊縮在一行內

留言