PHP - json_encode 生成器对象(使用 yield)
PHP - json_encode a generator object (using yield)
我在 PHP (5.6) 中有一个动态生成的非常大的数组,我想将其转换为 JSON。问题是数组太大,内存放不下——当我尝试处理它时出现致命错误(内存耗尽)。所以我发现,使用生成器,内存问题就会消失。
这是我迄今为止尝试过的代码(这个简化的示例显然不会产生内存错误):
<?php
function arrayGenerator()// new way using generators
{
for ($i = 0; $i < 100; $i++) {
yield $i;
}
}
function getArray()// old way, generating and returning the full array
{
$array = [];
for ($i = 0; $i < 100; $i++) {
$array[] = $i;
}
return $array;
}
$object = [
'id' => 'foo',
'type' => 'blah',
'data' => getArray(),
'gen' => arrayGenerator(),
];
echo json_encode($object);
但是 PHP 似乎没有 JSON 对来自生成器的值进行编码。这是我从之前的脚本中得到的输出:
{
"id": "foo",
"type": "blah",
"data": [// old way - OK
0,
1,
2,
3,
//...
],
"gen": {}// using generator - empty object!
}
是否可以在调用 json_encode
之前对生成器生成的数组进行 JSON 编码而不生成完整序列?
什么是生成器函数?
生成器函数实际上是一种更紧凑、更高效的编写 Iterator. It allows you to define a function that will calculate and return values while you are looping over it:
的方法
也根据 http://php.net/manual/en/language.generators.overview.php
中的文档
Generators provide an easy way to implement simple iterators without the overhead or complexity of implementing a class that implements the Iterator interface.
A generator allows you to write code that uses foreach to iterate over a set of data without needing to build an array in memory, which may cause you to exceed a memory limit, or require a considerable amount of processing time to generate. Instead, you can write a generator function, which is the same as a normal function, except that instead of returning once, a generator can yield as many times as it needs to in order to provide the values to be iterated over.
什么是 yield
?
yield
关键字returns data from a generator function:
The heart of a generator function is the yield keyword. In its simplest form, a yield statement looks much like a return statement, except that instead of stopping execution of the function and returning, yield instead provides a value to the code looping over the generator and pauses execution of the generator function.
因此,在您的情况下,要生成预期输出,您需要使用 foreach
循环或 iterator
迭代 arrayGenerator()
函数的输出,然后再将其处理为 json(如@apokryfos 建议)
不幸的是,json_encode 无法从生成器函数生成结果。使用 iterator_to_array
仍会尝试创建整个数组,这仍会导致内存问题。
您将需要创建您的函数,该函数将从生成器函数生成 json 字符串。这是一个外观示例:
function json_encode_generator(callable $generator) {
$result = '[';
foreach ($generator as $value) {
$result .= json_encode($value) . ',';
}
return trim($result, ',') . ']';
}
它不是一次对整个数组进行编码,而是一次只对一个对象进行编码,然后将结果连接成一个字符串。
上面的例子只负责对数组进行编码,但它可以很容易地扩展到对整个对象进行递归编码。
如果创建的字符串仍然太大而无法放入内存,那么您唯一剩下的选择就是直接使用输出流。这是它的样子:
function json_encode_generator(callable $generator, $outputStream) {
fwrite($outputStream, '[');
foreach ($generator as $key => $value) {
if ($key != 0) {
fwrite($outputStream, ',');
}
fwrite($outputStream, json_encode($value));
}
fwrite($outputStream, ']');
}
如您所见,唯一的区别是我们现在使用 fwrite
来写入传入的流而不是连接字符串,我们还需要以不同的方式处理尾随逗号.
我在 PHP (5.6) 中有一个动态生成的非常大的数组,我想将其转换为 JSON。问题是数组太大,内存放不下——当我尝试处理它时出现致命错误(内存耗尽)。所以我发现,使用生成器,内存问题就会消失。
这是我迄今为止尝试过的代码(这个简化的示例显然不会产生内存错误):
<?php
function arrayGenerator()// new way using generators
{
for ($i = 0; $i < 100; $i++) {
yield $i;
}
}
function getArray()// old way, generating and returning the full array
{
$array = [];
for ($i = 0; $i < 100; $i++) {
$array[] = $i;
}
return $array;
}
$object = [
'id' => 'foo',
'type' => 'blah',
'data' => getArray(),
'gen' => arrayGenerator(),
];
echo json_encode($object);
但是 PHP 似乎没有 JSON 对来自生成器的值进行编码。这是我从之前的脚本中得到的输出:
{
"id": "foo",
"type": "blah",
"data": [// old way - OK
0,
1,
2,
3,
//...
],
"gen": {}// using generator - empty object!
}
是否可以在调用 json_encode
之前对生成器生成的数组进行 JSON 编码而不生成完整序列?
什么是生成器函数?
生成器函数实际上是一种更紧凑、更高效的编写 Iterator. It allows you to define a function that will calculate and return values while you are looping over it:
的方法也根据 http://php.net/manual/en/language.generators.overview.php
中的文档Generators provide an easy way to implement simple iterators without the overhead or complexity of implementing a class that implements the Iterator interface.
A generator allows you to write code that uses foreach to iterate over a set of data without needing to build an array in memory, which may cause you to exceed a memory limit, or require a considerable amount of processing time to generate. Instead, you can write a generator function, which is the same as a normal function, except that instead of returning once, a generator can yield as many times as it needs to in order to provide the values to be iterated over.
什么是 yield
?
yield
关键字returns data from a generator function:
The heart of a generator function is the yield keyword. In its simplest form, a yield statement looks much like a return statement, except that instead of stopping execution of the function and returning, yield instead provides a value to the code looping over the generator and pauses execution of the generator function.
因此,在您的情况下,要生成预期输出,您需要使用 foreach
循环或 iterator
迭代 arrayGenerator()
函数的输出,然后再将其处理为 json(如@apokryfos 建议)
不幸的是,json_encode 无法从生成器函数生成结果。使用 iterator_to_array
仍会尝试创建整个数组,这仍会导致内存问题。
您将需要创建您的函数,该函数将从生成器函数生成 json 字符串。这是一个外观示例:
function json_encode_generator(callable $generator) {
$result = '[';
foreach ($generator as $value) {
$result .= json_encode($value) . ',';
}
return trim($result, ',') . ']';
}
它不是一次对整个数组进行编码,而是一次只对一个对象进行编码,然后将结果连接成一个字符串。
上面的例子只负责对数组进行编码,但它可以很容易地扩展到对整个对象进行递归编码。
如果创建的字符串仍然太大而无法放入内存,那么您唯一剩下的选择就是直接使用输出流。这是它的样子:
function json_encode_generator(callable $generator, $outputStream) {
fwrite($outputStream, '[');
foreach ($generator as $key => $value) {
if ($key != 0) {
fwrite($outputStream, ',');
}
fwrite($outputStream, json_encode($value));
}
fwrite($outputStream, ']');
}
如您所见,唯一的区别是我们现在使用 fwrite
来写入传入的流而不是连接字符串,我们还需要以不同的方式处理尾随逗号.