json_decode對JSON格式的字串進行解碼,接受一個JSON格式的字串並且把它轉換為PHP變數。但是運行json_decode可能會出現超出記憶體上線,這是該怎麼辦?
如果你用PHP的json_decode函數解析JSON字串,而且JSON字串中包含一個大量元素的數組,那你就要小心解析過程中PHP超出記憶體上限了。
筆者在開發時遇到一個JSON檔案需要解析,該JSON中包含了一個由眾多MAC位址組成的數組,像這樣:
{ "name": "MAC File", "date": "2017-11-08", "macList": [ "11-11-11-11-11-11", "22-22-22-22-22-22", ... ] }
結果json_decode過程中超出了PHP默認的128M內存上限。
WHAT,超限了? !這張JSON檔才 10M 啊!
在大罵「這函數是不是有BUG」之後,仔細推敲下,發現問題出在MAC位址構成的陣列上。要知道PHP的陣列是很吃記憶體的。
PHP數組究竟多吃內存,可以做個簡單的試驗,向數組內放入50萬個MAC地址,打印內存佔用量:
$a = []; for ($i = 0; $i !== 500000; $i++) { $a[] = '11-11-11-11-11-11'; } echo memory_get_usage() . PHP_EOL;
如果把這些MAC地址寫在一個文件裡,理論上只佔9.6M 磁碟空間,但是PHP數組維護相同的訊息,卻佔了72.4M 記憶體。
有辦法解決json_deocde時的記憶體超限嗎?當然,簡單粗暴點的話就把記憶體上限調高:
ini_set('memory_limit','1024M');
雖然可行,但會引發一個問題,就是:
可能被其他語言的工程師嘲笑PHP佔內存。
那有沒有更明智的辦法解決記憶體超限問題?
有。因為PHP數組佔用記憶體很大,所以我們要避免json_decode在解碼時產生巨大數組。怎麼做呢?這就要從JSON的編碼格式下手,例如可以把巨大的JSON數組修改成字串:
{ "name": "MAC File", "date": "2017-11-08", "macList": "11-11-11-11-11-11,22-22-22-22-22-22,...", }
我把macList從數組轉換成以逗號分割的字串。這樣可以避免json_decde產生一個巨大無比的數組,取而代之的是一個超長的字串。
字串佔用的記憶體量可比陣列小多了,剛才50萬個MAC位址,瞬間只佔9.7M的記憶體。修改完之後,json_decode順利解析成功,而且解析速度更快了。
原先macList是個數組,可以透過foreach遍歷其中的元素,現在是字串了,該怎麼遍歷呢?
不難,可以用strtok:
$tok = strtok($macList, ','); while ($tok !== false) { $mac = $tok; $tok = strtok(','); }
遍歷的難度沒有增加多少,對吧?
你可能會問,這種方法可以對付簡單的JSON數組,如果一個JSON數組的每個元素是個JSON對象,該怎麼辦?
我們可以這樣建構字串:
{ "list": '{"name":"obj1"}###{"name":"obj2"}###...' }
字串由一個小的JSON組成,它們之間用特殊的標記
分割。在解析時,根據特殊標記切分出一個個JSON對象,再用json_decode逐個解析:$tok = strtok($objectList, '###'); // 按###切割 while ($tok !== false) { $objectStr = $tok; // 每切割出一个JSON对象就解码 $object = json_decode($objectStr, true); $tok = strtok('###'); }你也可以自創一種編碼/解析這個超長字串的方法,總之最終目標就是避免json_decode在解碼過程中產生超大數組。 透過本文,你應該略到了PHP Array吃記憶體的能力。把JSON中的陣列替換成字串表示,可以節省大量的記憶體。我還跑了一個比較數據,給大家參考:解析50萬個MAC:
保存MAC位址方式 | 數組方式 | 字串方式 |
---|---|---|
JSON檔案大小 | 9.6M | 8.6M(每個元素省了一對引號) |
平均記憶體佔用 | 72.4M | #8.7M |
平均json_decode解析時間 | 0.73s | 0.41s |
#儲存MAC位址方式 | 陣列方式 | 字串方式 |
---|---|---|
JSON檔案大小 | 20M | 18M |
平均記憶體佔用 | 204.6M | #54.2M |
#平均json_decode解析時間 | ##1.61s | 0.81s |
解析200萬個MAC:
儲存MAC位址方式 | 陣列方式 | 字串方式 |
---|---|---|
JSON檔案大小 | 40M | 36M |
#平均記憶體佔用 | 409.0M | 108.2M |
平均json_decode解析時間 | 3.05s | 1.53s |
以上是小小一個改動,竟能讓json_decode記憶體節省70%?的詳細內容。更多資訊請關注PHP中文網其他相關文章!