【问题标题】:PHP - json_encode a generator object (using yield)PHP - json_encode 生成器对象(使用产量)
【发布时间】:2016-07-26 13:39:35
【问题描述】:

我在 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 编码。这是我从 previuos 脚本得到的输出:

{
    "id": "foo",
    "type": "blah",
    "data": [// old way - OK
        0,
        1,
        2,
        3,
        //...
    ],
    "gen": {}// using generator - empty object!
}

在我调用json_encode之前,是否可以在不生成完整序列的情况下对生成器生成的数组进行 JSON 编码?

【问题讨论】:

  • 编码整个序列的唯一方法是生成整个序列。在后台,这将需要发生。如果你想让生成器成为一个可用的数组,你可以使用iterator_to_array(arrayGenerator())
  • 使用该功能我再次遇到同样的问题 - 内存耗尽。目前我唯一能做的就是拆分数组或增加内存限制(不是我正在寻找的解决方案......)。
  • 真正生成不适合内存的 JSON 数据的唯一方法是对其进行。为此,您将 a) 需要一个流式 JSON 生成器(PHP 没有内置)和 b) 立即将结果流式传输到某处,例如到标准输出、文件或下载文件的 Web 服务器。将结果连接到内存中的字符串并将其存储在变量中将具有相同的内存问题。
  • 其实,这可能是你想要的:Streaming parser for JSON collections.

标签: php json generator yield


【解决方案1】:

很遗憾,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 写入传入的流而不是连接字符串,而且我们还需要以不同的方式处理尾随逗号。

【讨论】:

  • 当然这仍然会在内存中生成大量的JSON,可能比原始数据还要大...
  • 嗯,字符串比 PHP 中的数组更节省内存,所以上面的解决方案可能就足够了。否则,您将不得不直接使用输出流,而不是将其临时存储在字符串中。不管是字符串还是流,逻辑都是一样的。
【解决方案2】:

什么是生成器函数?

生成器函数实际上是编写Iterator 的更紧凑和有效的方式。它允许你定义一个函数,计算并返回你是looping over it:

同样根据来自http://php.net/manual/en/language.generators.overview.php的文档

生成器提供了一种简单的方法来实现简单的迭代器,而无需实现实现 Iterator 接口的类的开销或复杂性。

生成器允许您编写使用 foreach 迭代一组数据的代码,而无需在内存中构建数组,这可能会导致您超出内存限制,或者需要大量的处理时间来生成。相反,您可以编写一个生成器函数,它与普通函数相同,只是生成器不是返回一次,而是生成器可以根据需要多次生成以提供要迭代的值。

什么是yield

yield 关键字 returns data from a generator function:

生成器函数的核心是 yield 关键字。在最简单的形式中,yield 语句看起来很像 return 语句,只是它不是停止函数的执行并返回,而是为在生成器上循环的代码提供一个值并暂停生成器函数的执行。

因此,在您的情况下,要生成预期的输出,您需要使用foreach 循环或iterator 迭代arrayGenerator() 函数的输出,然后再将其处理为json(如@apokryfos 所建议)

【讨论】:

猜你喜欢
  • 2015-07-26
  • 1970-01-01
  • 2012-08-17
  • 2014-03-03
  • 2020-04-29
  • 1970-01-01
  • 2014-12-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多