一、什么是Iterable接口
Iterable接口是PHP 7.1中新增加的一个接口,用来表示一个类是否能够被迭代。一个类只要实现了Iterator或者IteratorAggregate接口,就可以被foreach迭代。
实现Iterator接口需要实现以下4个方法:
class MyIterator implements Iterator { private $items = []; private $currentIndex = 0; public function __construct(array $items) { $this->items = $items; } public function rewind() { $this->currentIndex = 0; } public function current() { return $this->items[$this->currentIndex]; } public function key() { return $this->currentIndex; } public function next() { $this->currentIndex++; } public function valid() { return isset($this->items[$this->currentIndex]); } }
实现IteratorAggregate接口需要实现一个getIterator方法,返回一个实现了Iterator接口的对象,例如:
class MyIterable implements IteratorAggregate { private $items = []; public function __construct(array $items) { $this->items = $items; } public function getIterator() { return new MyIterator($this->items); } }
二、Iterable接口的作用
Iterable接口的作用是可以让我们自定义的数据结构通过foreach语法进行遍历。
例如下面这个例子,我们可以实现一个自定义的链表数据结构,并使用foreach语法遍历链表:
class ListNode { public $val = 0; public $next = null; public function __construct($val) { $this->val = $val; } } class LinkedList implements IteratorAggregate { public $head = null; public $tail = null; public $count = 0; public function add($node) { if ($this->head == null) { $this->head = $node; $this->tail = $node; } else { $this->tail->next = $node; $this->tail = $node; } $this->count++; } public function getIterator() { $node = $this->head; while ($node) { yield $node; $node = $node->next; } } } $list = new LinkedList(); $list->add(new ListNode(1)); $list->add(new ListNode(2)); $list->add(new ListNode(3)); foreach ($list as $node) { echo $node->val . " "; } // output: 1 2 3
通过实现IteratorAggregate接口,并使getIterator方法返回一个生成器,就可以实现在自定义的链表数据结构上使用foreach语法遍历。
三、Iterable接口的注意事项
在实现IteratorAggregate接口时,如果返回的Iterator没有实现Rewindable接口,那么键为0的元素不会被遍历。
例如下面的例子:
class MyIterator implements Iterator { private $count = 3; private $currentIndex = 0; public function rewind() { $this->currentIndex = 0; } public function current() { return $this->currentIndex; } public function key() { return $this->currentIndex; } public function next() { $this->currentIndex++; $this->count--; } public function valid() { return $this->count > 0; } } class MyIterable implements IteratorAggregate { public function getIterator() { return new MyIterator(); } } $iterable = new MyIterable(); foreach ($iterable as $key => $value) { echo "{$key} => {$value}\n"; } // 输出为1 => 1, 2 => 2
在这个例子中,$key为0的元素并没有被遍历,这是因为MyIterator没有实现Rewindable接口。如果要使所有元素都被遍历,需要在MyIterator中实现rewind方法。
四、Iterable接口的扩展
除了Iterator和IteratorAggregate接口,PHP还提供了Generator接口,它让我们可以在调用生成器函数时,使用foreach语法一样地对生成器提供迭代器。同时,Generator方法可以实现中断和恢复执行的能力,可以用于处理大量数据进行分批次处理。
以数据分块为例,下面是处理csv文件时,使用Generator实现文件分块处理的一个例子:
function readData($filename, $blockSize) { if (!$handle = fopen($filename, 'r')) { throw new InvalidArgumentException("Cannot open file ($filename)"); } $lineCount = 0; $block = ""; while (!feof($handle)) { $line = fgets($handle); $block .= $line; $lineCount++; if ($lineCount >= $blockSize) { $lineCount = 0; yield $block; $block = ""; } } if (!empty($block)) { yield $block; } fclose($handle); } $filename = "large.csv"; $blockSize = 1000; foreach (readData($filename, $blockSize) as $block) { // process each block of data }此处是一个基于Generator实现的分块处理csv文件的例子,可以使用foreach语法一样地对生成器提供迭代器,同时也支持生成器的中断和恢复执行的能力,可以用于处理大量数据进行分批次处理。
五、总结
Iterable接口是PHP 7.1新增的一个接口,使用Iterable接口可以实现自定义的数据结构在遍历时使用foreach语法。通过实现Iterator或IteratorAggregate接口,并使getIterator方法返回一个可迭代的对象,就可以在自定义的数据结构上使用foreach语法。
除了Iterator和IteratorAggregate接口,PHP还提供了Generator接口,可以在调用生成器函数时,使用foreach语法一样地对生成器提供迭代器。Generator方法可以实现中断和恢复执行的能力,可以用于处理大量数据进行分块处理。