Вопрос проверяет, понимает ли кандидат, чем генераторы концептуально отличаются от массивов и классических итераторов, особенно в части потребления памяти и поведения при повторном обходе.
Генераторы создают значения "по требованию" и не хранят всю последовательность в памяти, тогда как массивы содержат все элементы сразу. Это делает генераторы гораздо более экономными по памяти при работе с большими данными. В отличие от многих массивоподобных структур, генератор обычно нельзя "перемотать" назад и пройти повторно — после завершения он исчерпывается. По сравнению с вручную реализованными итераторами, генераторы проще в коде, но дают меньше контроля над внутренним состоянием и перемоткой.
Генераторы решают ту же задачу итерации по данным, но делают это другим способом, что влияет на память и поведение при обходе.
Массивы и генераторы принципиально по-разному хранят данные.
Определение: Массив в PHP — это структура, которая хранит все элементы в памяти одновременно.
Определение: Генератор — это объект, который вычисляет и отдаёт элементы по мере запроса, а не хранит их все сразу.
Сравнение:
Массивы
Все элементы уже есть в памяти.
Операции индексного доступа ($arr[1000]) быстрые, если элемент существует.
Если данных очень много, легко превысить лимит памяти.
Генераторы
Значения создаются "на лету" при обходе.
В памяти одновременно находится только текущее значение (и небольшое состояние функции).
Можно работать с миллионными наборами данных или бесконечными последовательностями.
Пример отличия:
php
// А: массив — хранит все числа сразу
function buildArray(int $n): array
{
$result = [];
for ($i = 0; $i < $n; $i++) {
$result[] = $i;
}
return $result; // большой массив в памяти
}
// Б: генератор — выдаёт числа по одному
function buildGenerator(int $n): Generator
{
for ($i = 0; $i < $n; $i++) {
yield $i; // значение по одному
}
}
При n = 1_000_000 массив может занять сотни мегабайт памяти, а генератор — существенно меньше.
Iterator)До появления генераторов для сложной логики обхода приходилось писать собственные классы, реализующие Iterator или IteratorAggregate.
Определение: Итератор — объект, который предоставляет методы current, next, key, valid, rewind для пошагового обхода.
И генераторы, и "ручные" итераторы:
позволяют использовать foreach
реализуют интерфейс Traversable
могут выдавать потенциально бесконечные/большие последовательности
Простота реализации
Генератор пишется в несколько строк с yield, а итератор — отдельным классом с несколькими методами.
Явное управление перемоткой у итераторов
В ручном итераторе вы сами пишете rewind(), и можете настроить, как именно он "перематывается":
php
class MyIterator implements Iterator {
public function rewind() { /* курсор в начало */ }
public function current() { /* текущее значение */ }
public function key() { /* текущий ключ */ }
public function next() { /* шаг вперёд */ }
public function valid() { /* есть ли элемент */ }
}
У генератора rewind() вызывается автоматически и работает один раз при первом входе в генератор.
Гибкость состояния
В итераторе вы сами управляете внутренним состоянием (например, ресурсами соединений, чтением файла), а в генераторе это состояние обычно привязано к стеку вызовов функции. Для большинства задач этого достаточно, но очень кастомное поведение иногда проще реализовать итератором.
Ключевое свойство генераторов — они, как правило, одноразовые:
Вы создаёте генератор:
php
$gen = buildGenerator(3);
Идёте по нему foreach — значения выдаются по очереди.
После окончания генератор "исчерпан" — повторно пройти по нему нельзя (без создания нового генератора).
Пример:
php
$gen = buildGenerator(3);
foreach ($gen as $v) {
echo $v; // 0 1 2
}
foreach ($gen as $v) {
echo $v; // ничего, генератор уже завершён
}
Это поведение важно понимать: генератор — это не "коллекция", а поток данных.
Если вам нужна перемотка — создавайте новый генератор заново или используйте структуру данных, которая хранит элементы (массив, коллекция).
Генераторы особенно полезны там, где:
Обрабатываются большие объёмы данных
чтение больших файлов построчно
обработка больших выборок из БД
стриминг данных из API
Последовательность логически бесконечна
генерация последовательностей чисел
потоки событий
Нужно уменьшить задержку
Можно выдавать первые элементы до того, как будут готовы все остальные (например, стриминг).
Генераторы отличаются от массивов и обычных итераторов тем, что:
не хранят все данные в памяти сразу
часто являются одноразовыми и не поддерживают "перемотку" после завершения
проще в написании, чем классы-итераторы, но дают меньше ручного контроля
Использовать генераторы стоит тогда, когда для вас важнее экономия памяти и потоковая обработка данных, а не произвольный доступ и многократный повторный обход без пересоздания источника.