PHP 迭代器与生成器详解
迭代器 (Iterator)
迭代器是一种设计模式,提供一种方法顺序访问聚合对象中的元素,而不暴露内部表示。

内置迭代器接口
interface Iterator extends Traversable {
public function current(); // 返回当前元素
public function key(); // 返回当前键
public function next(); // 移动到下一个元素
public function rewind(); // 重置迭代器
public function valid(); // 检查当前位置是否有效
}
自定义迭代器示例
class MyCollection implements Iterator {
private $items = [];
private $position = 0;
public function __construct(array $items) {
$this->items = $items;
}
public function current() {
return $this->items[$this->position];
}
public function key() {
return $this->position;
}
public function next() {
++$this->position;
}
public function rewind() {
$this->position = 0;
}
public function valid() {
return isset($this->items[$this->position]);
}
}
// 使用示例
$collection = new MyCollection(['a', 'b', 'c']);
foreach ($collection as $key => $value) {
echo "$key: $value\n";
}
使用 ArrayIterator
$data = ['name' => 'John', 'age' => 30, 'city' => 'Beijing'];
$iterator = new ArrayIterator($data);
foreach ($iterator as $key => $value) {
echo "$key: $value\n";
}
生成器 (Generator)
生成器是PHP 5.5引入的功能,提供了一种更简单的方式实现迭代器,无需编写完整的Iterator类。
基本语法
function getNumbers() {
for ($i = 1; $i <= 5; $i++) {
yield $i;
}
}
$generator = getNumbers();
foreach ($generator as $number) {
echo $number . "\n";
}
带键值对的生成器
function getUserData() {
yield 'id' => 1;
yield 'name' => 'Alice';
yield 'email' => 'alice@example.com';
}
foreach (getUserData() as $key => $value) {
echo "$key: $value\n";
}
引用生成器
function &getReference() {
$value = 0;
while ($value < 3) {
yield $value;
$value++;
}
}
$gen = getReference();
foreach ($gen as &$value) {
$value *= 2;
echo $value . "\n"; // 输出: 0, 2, 4
}
生成器的高级用法
从数组生成
function generateFromArray(array $items) {
foreach ($items as $key => $value) {
yield $key => $value;
}
}
$items = ['a', 'b', 'c'];
$gen = generateFromArray($items);
或者使用 yield from:
```php
function generateWithPrefix(array $items) {
yield from $items; // 直接将数组元素yield出去
}
// yield from 也可以用于委托另一个生成器
function generateEven() {
for ($i = 2; $i <= 10; $i += 2) {
yield $i;
}
}
function generateOdd() {
for ($i = 1; $i <= 9; $i += 2) {
yield $i;
}
}
function generateAllNumbers() {
yield from generateEven();
yield from generateOdd();
}
发送值到生成器
function inputReceiver() {
$output = '';
while (true) {
$input = yield $output;
$output = "Received: " . $input;
}
}
$gen = inputReceiver();
echo $gen->current() . "\n"; // 初始输出: ""
echo $gen->send('Hello') . "\n"; // 输出: Received: Hello
echo $gen->send('World') . "\n"; // 输出: Received: World
实际应用场景
处理大文件
function readLargeFile($filename) {
$handle = fopen($filename, 'r');
if ($handle === false) {
throw new Exception('Cannot open file');
}
while (($line = fgets($handle)) !== false) {
yield trim($line);
}
fclose($handle);
}
// 逐行处理1GB的大文件
foreach (readLargeFile('huge_log.txt') as $line) {
processLine($line);
}
分页数据生成
class PaginatedData {
private $totalPages;
public function __construct($totalPages) {
$this->totalPages = $totalPages;
}
public function getData($page) {
// 模拟API调用
return range(($page-1)*10 + 1, $page*10);
}
public function getAllData() {
for ($page = 1; $page <= $this->totalPages; $page++) {
$data = $this->getData($page);
foreach ($data as $item) {
yield $item;
}
}
}
}
$paginated = new PaginatedData(3);
foreach ($paginated->getAllData() as $data) {
echo $data . " "; // 1 2 3 ... 30
}
迭代器 vs 生成器 对比
| 特性 | 迭代器 | 生成器 |
|---|---|---|
| 内存使用 | 所有数据加载到内存 | 按需生成,内存友好 |
| 实现复杂度 | 需要实现多个方法 | 简单,函数+ yield |
| 可重用性 | 可以重置和多次遍历 | 不可重置,只能遍历一次 |
| 功能完整度 | 完整迭代器功能 | 有限功能(无反向迭代等) |
| 适用场景 | 小数据量、需要多次遍历 | 大数据量、单向遍历 |
| 性能 | 初始化需要内存 | 初始化快速,延迟执行 |
最佳实践
// 1. 使用生成器处理无限序列
function fibonacci() {
$a = 0;
$b = 1;
while (true) {
yield $a;
[$a, $b] = [$b, $a + $b];
}
}
// 2. 结合 array_walk 和生成器
function arrayWalkWithGenerator(array $array, callable $callback) {
foreach ($array as $key => $value) {
yield $callback($value, $key);
}
}
// 3. 延迟执行过滤
function filterGenerator(callable $callback, iterable $items) {
foreach ($items as $item) {
if ($callback($item)) {
yield $item;
}
}
}
// 使用示例
$numbers = [1, 2, 3, 4, 5, 6];
$evenNumbers = filterGenerator(function($n) {
return $n % 2 === 0;
}, $numbers);
foreach ($evenNumbers as $num) {
echo $num . " "; // 2 4 6
}
注意事项
- 生成器不可重置: 一旦遍历完成,需要重新创建生成器
- 参考传递: yield by reference 时注意作用域
- 异常处理: 生成器中的异常会被正常捕获
- 性能考虑: 对于小数据集,可能不如数组直接遍历快
- PHP版本: yield from 需要 PHP 7.0+
选择合适的迭代方式能显著提高代码的可读性和性能,特别是在处理大数据集时,生成器的内存优势尤为明显。