在我的应用程序中,我经常调用一个返回json字符串的外部api。
$url = 'api.example.com/xyz';
$blah = json_decode( file_get_contents( $url ) );
但在某些情况下,我得到了
PHP致命错误:允许的内存大小xxx字节耗尽(试图分配32个字节)...
我无法控制外部API,当然我可以增加php的内存,但这有一些缺点。
1-无论我设定的尺寸如何,仍然可能太少。 2-如果我将内存大小设置为“无限”,那么我可能会冒着杀死我的服务器的风险。
理想情况下,我想在调用json_decode(...)之前'检查'字符串会导致内存耗尽。
这可能吗?
答案 0 :(得分:6)
如果他们设法耗尽服务器的内存,你必须得到一些大规模的JSON响应。以下是包含多维关联数组的1 MB文件的一些度量标准(包含为进入具有不同数据类型的三个MySQL表而准备的数据)。
当我include
并且文件作为数组加载到内存中时,我的内存使用量变为9 MB。如果我使用file_get_contents()
获取原始数据,则需要1 MB内存。然后,PHP数组与数据的strlen()
的比率大约为1:9(最初用var_export()
输出)。
当我运行json_encode()
时,峰值内存使用量不会增加。 (PHP以块的形式分配内存,因此通常会有一些开销,在这种情况下足以包含JSON的字符串数据;但它可能会使您更多地阻塞一个块。)生成的JSON数据作为字符串需要670 KB。
当我将带有file_get_contents
的JSON数据加载到字符串中时,它需要0.75 MB的内存。当我在其上运行json_decode()
时,它需要7 MB的内存。然后,我将因 JSON-data-bytesize 解码为本地PHP数组或对象的RAM要求的最小比率为1:10。
要在解码之前对JSON数据运行测试,您可以执行以下操作:
if (strlen($my_json) * 10 > ($my_mb_memory * 1024 * 1024)) {
die ('Decoding this would exhaust the server memory. Sorry!');
}
...其中$my_json
是原始JSON响应,$my_mb_memory
是您分配的RAM,它被转换为字节以与传入数据进行比较。 (当然,您也可以使用intval(ini_get('memory_limit'))
将内存限制作为整数。)
如下所述,RAM的使用还取决于您的数据结构。相比之下,还有一些更快的测试用例,因为我很好奇自己:
所以你的实际内存里程可能会有很大差异。另外要注意的是,如果你将大量数据传递到圈子中并做一些这样的事情,你的内存使用量可能会增加很多(或指数地,取决于您的代码经济)高于单独json_decode()
将导致的。
要调试内存使用情况,您可以在代码中以主要间隔使用memory_get_usage()
和/或memory_get_peak_usage()
来记录或输出代码不同部分中使用的内存。
答案 1 :(得分:2)
我上面的第一个答案纯粹是关于避免内存限制。 现在,如果你不想放弃某些,你如何处理数据,但是如果它偶尔会被丢弃笨重超出你的记忆限制?
假设您不需要在一次性和绝对实时中解析响应。然后,您可以简单地将响应拆分为适当大小的块,例如使用explode()
或preg_split()
,并将它们保存到临时目录中,稍后在批处理操作中处理。
我假设大型API响应一次返回多个数据集;如果没有,你还可以将一个多维条目拼接成更易于管理的块,这些块稍后会重新加入,尽管这需要更多的手术精度来制作你的JSON字符串分割器函数。
如果需要在以后的处理中关联多个数据集(例如数据库条目),您还需要一个包含批处理操作的元数据的聚合器文件。 (或者将其全部粘贴到数据库中。)您当然必须确保分块数据格式正确。这不是理想的,但没有记忆的演出也不理想。批处理是处理它的一种方式。
答案 2 :(得分:2)
如果JSON文件太大,您可以使用基于事件的JSON解析器(如https://github.com/salsify/jsonstreamingparser)处理任意大小的JSON文件,而不是简单地退出。一次只能将一小部分对象/数组加载到内存中。
答案 3 :(得分:0)
如果您唯一需要做的就是遍历大小无法确定的json中的项目,请尝试halaxa/json-machine。解析任何大小的json时,它都不会耗尽内存,仅使用foreach
即可做到这一点,没有火箭科学。无需事先检查大小“安全性”,也无需增加php内存限制。它是这样的:
<?php
foreach(JsonMachine::fromFile('users.json') as $user) {
echo $user['name'];
}