У меня очень большой массив в 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-кодирование массива, созданного генератором, без генерации полной последовательности, прежде чем я вызову json_encode
?
К сожалению, 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
для записи в переданный поток вместо конкатенации строк, мы также должны по-другому позаботиться о конечной запятой.
Функция генератора — фактически более компактный и эффективный способ написать Итератор. Это позволяет вам определить функцию, которая будет рассчитать и вернуть ценности в то время как ты зацикливаясь на этом:
Также согласно документу от http://php.net/manual/en/language.generators.overview.php
Генераторы предоставляют простой способ реализации простых итераторов без дополнительных затрат и сложности реализации класса, реализующего интерфейс итератора.
Генератор позволяет вам писать код, который использует foreach для итерации по набору данных без необходимости создания массива в памяти, что может привести к превышению лимита памяти или значительному времени обработки для генерации. Вместо этого вы можете написать функцию генератора, которая аналогична обычной функции, за исключением того, что вместо однократного возврата генератор может выдавать столько раз, сколько ему нужно, чтобы предоставить значения для повторения.
yield
? yield
ключевое слово возвращает данные из функции генератора:
Сердцем функции генератора является ключевое слово yield. В своей простейшей форме оператор yield очень похож на оператор return, за исключением того, что вместо остановки выполнения функции и возврата yield вместо этого предоставляет значение для кода, зацикливающегося над генератором, и приостанавливает выполнение функции генератора.
Таким образом, в вашем случае, чтобы генерировать ожидаемый результат, вам нужно повторить вывод arrayGenerator()
функция с помощью foreach
петля или iterator
перед обработкой его в json (как предложено @apokryfos)