如果你用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 |
解析100萬個MAC:
保存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 |
如果對您的工作有幫助,還請幫忙點個贊:-)