对于迭代器,咱们在之前设计模式相干的文章中曾经讲过迭代器具体是个啥,而且也应用过 SPL 的例子来演示过,要是没有看过之前的文章的能够穿梭回去看一下哦!PHP设计模式之迭代器模式:https://mp.weixin.qq.com/s/uycac0OXYYjAG1BlzTUjsw。

因而,对于迭代器的概念,咱们这里就不会多说了,明天的次要内容就是来理解一下 SPL 扩大中都蕴含哪些迭代器以及它们的性能成果。另外,上一篇文章中咱们接触过的数组迭代器 ArrayIterator 因为曾经学习过了,也就不放在这里讲了。此外还有文件目录相干的迭代器,也会放在和其相干的文件目录操作的文章中解说,包含上面学习的这些迭代器还有不少都有绝对应的 递归式 的迭代器,比方咱们上面要讲到的 CachingIterator 、 FilterIterator 等等,都有它们对应的 RecursiveCachingIterator 、 RecursiveFilterIterator 类,这个大家就本人去钻研下吧,带递归迭代器,也就是多了两个办法 getChildren() 和 hasChildren() 而已,最初咱们还会实现一个本人的迭代器类,其中就会讲到递归这块。

IteratorIterator 包装迭代器

首先咱们来看一下什么是包装迭代器。它自身也是个迭代器,然而在实例化的时候,又必须给他再传递进来一个迭代器并保留在外部,是一个外部迭代器 InnerIterator 。对于它本身的那些迭代器接口函数来说,其实都是转发调用的那个外部迭代器相干的操作函数。感觉上其实就有点像是一个装璜器模式,咱们能够通过继承 IteratorIterator 来实现对原有迭代器性能的降级。

$iterator = new IteratorIterator(new ArrayIterator([1, 2, 3]));$iterator->rewind();while ($iterator->valid()) {    echo $iterator->key(), ": ", $iterator->current(), PHP_EOL;    $iterator->next();}// 0: 1// 1: 2// 2: 3

从代码中能够看出,它的结构参数必须还得是一个迭代器,自身的参数签名就是须要一个实现了 Traversable 接口的对象。Traversable 接口是所有迭代器所必须要实现的接口。

class OutIterator extends IteratorIterator{    public function rewind()    {        echo __METHOD__, PHP_EOL;        return parent::rewind();    }    public function valid()    {        echo __METHOD__, PHP_EOL;        return parent::valid();    }    public function current()    {        echo __METHOD__, PHP_EOL;        return parent::current() . '_suffix';    }    public function key()    {        echo __METHOD__, PHP_EOL;        return parent::key();    }    public function next()    {        echo __METHOD__, PHP_EOL;        return parent::next();    }    public function getInnerIterator()    {        echo __METHOD__, PHP_EOL;        return parent::getInnerIterator();    }}$iterator = new OutIterator(new ArrayIterator([1, 2, 3]));foreach ($iterator as $k => $v) {    echo $k, ': ', $v, PHP_EOL;}// OutIterator::rewind// OutIterator::valid// OutIterator::current// OutIterator::key// 0: 1_suffix// OutIterator::next// OutIterator::valid// OutIterator::current// OutIterator::key// 1: 2_suffix// OutIterator::next// OutIterator::valid// OutIterator::current// OutIterator::key// 2: 3_suffix// OutIterator::next// OutIterator::valid

咱们本人写了一个 OutIterator 类并继承自 IteratorIterator 类,而后重写所有迭代器相干的办法函数。在这些函数中,减少一些输入调试信息,最初通过 foreach 来遍历迭代器。能够看出,foreach 在判断对象是否可迭代后,就会像咱们应用 while 遍历迭代器一样地去调用对应的迭代器办法函数。这个例子相当地直观,也十分有助于咱们了解迭代器这堆办法函数到底在干嘛。

var_dump($iterator->getInnerIterator());// object(ArrayIterator)#5 (1) {//     ["storage":"ArrayIterator":private]=>//     array(3) {//       [0]=>//       int(1)//       [1]=>//       int(2)//       [2]=>//       int(3)//     }//   }

通过 getInerIterator() 办法咱们就能够取得包装迭代器外部的那个迭代器对象。这里很清晰地就能看到咱们给它外部搁置的那个迭代器相干的信息。

接下来,咱们再学习一些派生自 IteratorIterator 类的迭代器。也就是说,它们都是继承自 IteratorIterator 这个包装迭代器的,并在它的根底之上又减少了不少别致的性能。

AppendIterator 追加迭代器

追加迭代器,很奇怪的名字,先来看看它是做啥的。

$appendIterator = new AppendIterator();$appendIterator->append(new ArrayIterator([1, 2, 3]));$appendIterator->append(new ArrayIterator(['a' => 'a1', 'b' => 'b1', 'c' => 'c1']));var_dump($appendIterator->getIteratorIndex()); // int(0)foreach ($appendIterator as $k => $v) {    echo $k, ': ', $v, PHP_EOL;    echo 'iterator index: ', $appendIterator->getIteratorIndex(), PHP_EOL;}// 0: 1// iterator index: 0// 1: 2// iterator index: 0// 2: 3// iterator index: 0// a: a1// iterator index: 1// b: b1// iterator index: 1// c: c1// iterator index: 1var_dump($appendIterator->getIteratorIndex()); // NULL

是的,你没看错,这个追加迭代器的性能就是能够在它外面保留多个外部迭代器。咱们能够通过 append() 办法一直地增加,通过 getIteratorIndex() 能够查看到以后应用或遍历到的是哪个一个外部迭代器。

如果要获取外部迭代器对象的话,尽管也有继承自 IteratorIterator 的 getInnerIterator() 办法,但最好应用另一个办法。

var_dump($appendIterator->getArrayIterator());// object(ArrayIterator)#2 (1) {//     ["storage":"ArrayIterator":private]=>//     array(2) {//       [0]=>//       object(ArrayIterator)#7 (1) {//         ["storage":"ArrayIterator":private]=>//         array(3) {//           [0]=>//           int(1)//           [1]=>//           int(2)//           [2]=>//           int(3)//         }//       }//       [1]=>//       object(ArrayIterator)#9 (1) {//         ["storage":"ArrayIterator":private]=>//         array(3) {//           ["a"]=>//           string(2) "a1"//           ["b"]=>//           string(2) "b1"//           ["c"]=>//           string(2) "c1"//         }//       }//     }//   }

getArrayIterator() 能够一个数组模式的汇合来返回所有的外部迭代器。

CachingIterator 缓存迭代器

从英文名字就可以看进去,缓存迭代器。

$cachingIterator = new CachingIterator(new ArrayIterator([1, 2, 3]), CachingIterator::FULL_CACHE);var_dump($cachingIterator->getCache());// array(0) {// }foreach ($cachingIterator as $c) {}var_dump($cachingIterator->getCache());// array(3) {//     [0]=>//     int(1)//     [1]=>//     int(2)//     [2]=>//     int(3)//   }

它比拟有特色的就是这个 getCache() 办法,从下面的测试代码中大家看出什么问题了吗?没错,当咱们遍历一次迭代器之后,外部迭代器的数据信息会缓存到 getCache() 这个办法外面返回的数组中。咱们在遍历之前调用 getCache() 办法是没有任何内容的。另外,通过结构参数的第二个参数,咱们能够指定缓存数据的信息内容,在这里咱们应用的是 CachingIterator::FULL_CACHE ,也就是缓存全部内容。

FilterIterator 过滤迭代器

过滤这个词相熟不,array_filter() 这个函数也是针对数组进行过滤操作的。同样地,FilterIterator 迭代器也是实现相似的成果。不过在学习应用这个 FilterIterator 之前,咱们先学习一下它的两个派生类。

$callbackFilterIterator = new CallbackFilterIterator(new ArrayIterator([1, 2, 3, 4]), function ($current, $key, $iterator) {    echo $key, ': ', $current, PHP_EOL;    if ($key == 0) {        var_dump($iterator);    }    if ($current % 2 == 0) {        return true;    }    return false;});foreach ($callbackFilterIterator as $c) {    echo 'foreach: ', $c, PHP_EOL;}// 0: 1// object(ArrayIterator)#13 (1) {//   ["storage":"ArrayIterator":private]=>//   array(4) {//     [0]=>//     int(1)//     [1]=>//     int(2)//     [2]=>//     int(3)//     [3]=>//     int(4)//   }// }// 1: 2// foreach: 2// 2: 3// 3: 4// foreach: 4

CallbackFilterIterator 迭代器是通过咱们在结构参数的第二个参数指定的回调函数来进行过滤操作的一个迭代器。如果要让数据通过,返回 true ,否则就返回 false 。先讲这个迭代器正是因为它和 array_filter() 切实是太像了。array_filter() 也是一样的通过一个回调函数来进行过滤判断的。

$regexIterator = new RegexIterator(new ArrayIterator(['test1', 'test2', 'opp1', 'test3']), '/^(test)(\d+)/', RegexIterator::MATCH);var_dump(iterator_to_array($regexIterator));// array(3) {//     [0]=>//     string(5) "test1"//     [1]=>//     string(5) "test2"//     [3]=>//     string(5) "test3"//   }$regexIterator = new RegexIterator(new ArrayIterator(['test1', 'test2', 'opp1', 'test3']), '/^(test)(\d+)/', RegexIterator::REPLACE);$regexIterator->replacement = 'new $2$1'; var_dump(iterator_to_array($regexIterator));// array(3) {//     [0]=>//     string(9) "new 1test"//     [1]=>//     string(9) "new 2test"//     [3]=>//     string(9) "new 3test"//   }

RegexIterator 置信也不必多解释了,它就是通过正则表达式来进行过滤判断的。在这里须要留神的是,咱们应用了一个 iterator_to_array() 函数,它也是 SPL 中的一个函数,作用就是将迭代器转换为数组,其实也就是解决咱们都要写 foreach 或者 while 循环来演示的麻烦。

通过下面两个 FilterIterator 的派生类的学习,置信大家对于这个过滤迭代器更加有趣味了。不过,这个原始的 FilterIterator 是一个抽象类哦,也就是说,它是不能间接实例化的,咱们只能去再写一个类来继承它,并且要实现它的一个外围办法 accept() 。

class MyFilterIterator extends FilterIterator{    public function accept(){        echo  __METHOD__, PHP_EOL;        if($this->current()%2==0){            return true;        }        return false;    }}$myFilterIterator = new MyFilterIterator(new ArrayIterator([1,2,3,4]));var_dump(iterator_to_array($myFilterIterator));// MyFilterIterator::accept// MyFilterIterator::accept// MyFilterIterator::accept// MyFilterIterator::accept// array(2) {//   [1]=>//   int(2)//   [3]=>//   int(4)// }

不少小伙伴肯定曾经明确了,不论是下面的 CallbackFilterIterator 还是 RegexIterator ,都是实现了 FilterIterator 的一个实现类,它们都重写了 accept() 办法。它们通过构造函数的来传递须要的数据,在外围应用的过程中 CallbackFilterIterator 就是在 accept() 中调用了那个传递进来的回调办法,而 RegexIterator 则是在 accept() 中对外部迭代器的数据进行了正则表达式的判断。

InfiniteIterator 有限迭代器

有限迭代器?什么鬼,貌似很高大上。这是一个坑,要小心哦。

$infinateIterator = new InfiniteIterator(new ArrayIterator([1,2,3,4]));$i = 20;foreach($infinateIterator as $k=>$v){    echo $k, ': ', $v, PHP_EOL;    $i--;    if($i <= 0){        break;    }}// 0: 1// 1: 2// 2: 3// 3: 4// 0: 1// 1: 2// 2: 3// ………………// ………………

说白了,相似实现了让 next() 到最初一个数据的时候就将指针指回第一条数据的性能。有点像循环队列的感觉,也就是说,如果咱们没有限度条件的话,遍历这种有限迭代器,它将变成死循环始终不停地循环上来。

LimitIterator 数量限度迭代器

看名字就晓得了,就像咱们常常操作 MySQL 数据库做的翻页性能一样,LimitIterator 也是依据起始和偏移区间值返回一部分数据的。

$limitIterator = new LimitIterator(new ArrayIterator([1,2,3,4]),0,2);var_dump(iterator_to_array($limitIterator));// array(2) {//     [0]=>//     int(1)//     [1]=>//     int(2)//   }$limitIterator = new LimitIterator(new ArrayIterator([1,2,3,4]),1,3);var_dump(iterator_to_array($limitIterator));// array(3) {//     [1]=>//     int(2)//     [2]=>//     int(3)//     [3]=>//     int(4)//   }

NoRewindIterator 无重回迭代器

最初一个要介绍的 IteratorIterator 系列中的迭代器就是这个 NoRewindIterator 。同样地从名字中咱们能够看出一些端倪,那就是这个迭代器是没有 rewind() 办法的,或者说,这个办法是不起作用的。

$noRewindIterator = new NoRewindIterator(new ArrayIterator([1,2,3,4]));var_dump(iterator_to_array($noRewindIterator));// array(4) {//     [0]=>//     int(1)//     [1]=>//     int(2)//     [2]=>//     int(3)//     [3]=>//     int(4)//   }$noRewindIterator->rewind();var_dump(iterator_to_array($noRewindIterator));// array(0) {// }

后面咱们看到过,在 foreach() 时,每次遍历开始时都会调用 rewind() 办法让数据指针回到最顶部。同样地,iterator_to_array() 办法在其外部实现也是这样相似的步骤。但如果是 NoRewindIterator 的话,第二次遍历就不会有内容了,因为它的 rewind() 办法是不失效的,或者说是一个空的办法。

大家能够算大尝试用 while() 循环那种形式来测试一下,比应用 iterator_to_array() 更加清晰一些。

MultipleIterator 多并行迭代器

走出了 IteratorIterator 之后,咱们来看一个和它没什么关系的迭代器,也就是说,这个迭代器没有继承或者应用 IteratorIterator 相干的办法函数内容。

从名字来说,Multiple 是多个的意思,难道是外部放了多个迭代器?这不是和 AppendIterator 一样了。好吧,我抵赖,它的确在外部保留了一些迭代器,但留神,这些不是内置迭代器,和 IteratorIterator 是不同的哦。另外,它的表现形式也和 AppendIterator 不同。

$multipleIterator = new MultipleIterator();$multipleIterator->attachIterator(new ArrayIterator([1,2,3,4]));$multipleIterator->attachIterator(new ArrayIterator(['a' => 'a1', 'b' => 'b1', 'c' => 'c1']));$arr1 = new ArrayIterator(['a', 'b', 'c']);$arr2 = new ArrayIterator(['d', 'e', 'f', 'g', 'h']);$multipleIterator->attachIterator($arr1);$multipleIterator->attachIterator($arr2);var_dump($multipleIterator->containsIterator($arr1)); // bool(true)$multipleIterator->detachIterator($arr1);var_dump($multipleIterator->containsIterator($arr1)); // bool(false)// iterator_to_array($multipleIterator);foreach($multipleIterator as $k=>$v){    var_dump($k);    var_dump($v);}// array(3) {//     [0]=>//     int(0)//     [1]=>//     string(1) "a"//     [2]=>//     int(0)//   }//   array(3) {//     [0]=>//     int(1)//     [1]=>//     string(2) "a1"//     [2]=>//     string(1) "a"//   }//   array(3) {//     [0]=>//     int(1)//     [1]=>//     string(1) "b"//     [2]=>//     int(1)//   }//   array(3) {//     [0]=>//     int(2)//     [1]=>//     string(2) "b1"//     [2]=>//     string(1) "b"//   }//   array(3) {//     [0]=>//     int(2)//     [1]=>//     string(1) "c"//     [2]=>//     int(2)//   }//   array(3) {//     [0]=>//     int(3)//     [1]=>//     string(2) "c1"//     [2]=>//     string(1) "e"//   }

咱们能够通过 attachIterator() 增加迭代器,通过 containsIterator() 判断指定的迭代器是否存在,也能够通过 detachIterator() 删除某个迭代器。不过最次要的特点还是在遍历的后果。

不论是 key() 还是 current() ,返回的数据都是一个数组。其实这个数组就是每个迭代器对应的内容,比方第一个 key() 返回的是第一个迭代器的下标 0 的地位,第二个迭代器下标 a 和第三个迭代器下标 0 的地位。也就是说,它一次返回了所有迭代器第一个地位的下标信息。同理,current() 返回的也是以后这个地位的所有数据信息。

另外,咱们能够看到,不同的迭代器的外部数据数量是不同的,MultipleIterator 只会以起码的那条数据的数量进行返回,这个大家能够本人尝试下哦。

本人实现一个迭代器类

讲了那么多迭代器,咱们要不要本人也来简略地实现一个能够让 count() 失效的,并且有递归实现性能的,能够设置游标的迭代器。

class NewIterator implements Countable, RecursiveIterator, SeekableIterator {    private $array = [];    public function __construct($arr = []){        $this->array = $arr;    }    // Countable    public function count(){        return count($this->array);    }    // RecursiveIterator    public function hasChildren(){        if(is_array($this->current())){            return true;        }        return false;    }    // RecursiveIterator    public function getChildren(){                if(is_array($this->current())){            return new ArrayIterator($this->current());        }        return null;    }    // Seekable    public function seek($position) {        if (!isset($this->array[$position])) {            throw new OutOfBoundsException("invalid seek position ($position)");        }        $this->position = $position;    }            public function rewind() {          $this->position = 0;      }        public function current() {          return $this->array[$this->position];      }        public function key() {          return $this->position;      }        public function next() {          ++$this->position;      }        public function valid() {          return isset($this->array[$this->position]);      }}$newIterator = new NewIterator([1,2,3,4, [5,6,7]]);var_dump(iterator_to_array($newIterator));// array(5) {//     [0]=>//     int(1)//     [1]=>//     int(2)//     [2]=>//     int(3)//     [3]=>//     int(4)//     [4]=>//     array(3) {//       [0]=>//       int(5)//       [1]=>//       int(6)//       [2]=>//       int(7)//     }//   }var_dump(count($newIterator));// int(5)$newIterator->rewind();while($newIterator->valid()){    if($newIterator->hasChildren()){        var_dump($newIterator->getChildren());    }    $newIterator->next();}// object(ArrayIterator)#37 (1) {//     ["storage":"ArrayIterator":private]=>//     array(3) {//       [0]=>//       int(5)//       [1]=>//       int(6)//       [2]=>//       int(7)//     }//   }$newIterator->seek(2);while($newIterator->valid()){    var_dump($newIterator->current());    $newIterator->next();}// int(3)// int(4)// array(3) {//   [0]=>//   int(5)//   [1]=>//   int(6)//   [2]=>//   int(7)// }

对于代码不多解释了,正文里也有阐明,最次要的就是要实现 Countable, RecursiveIterator, SeekableIterator 这三个接口。它们别离对应的就是 count 能力、递归能力、设置游标的能力。

总结

货色不少吧,各种迭代器的实现能够说是 SPL 中的一个十分重要的内容。除了明天介绍的这些之外,还有别的一些迭代器咱们将在相干的文章中独立解说。光是明天的内容预计就不好消化了,放松排汇吧,飙车还在持续哦!

测试代码:

https://github.com/zhangyue0503/dev-blog/blob/master/php/2021/01/source/5.PHP的SPL扩大库(三)迭代器.php

参考文档:

https://www.php.net/manual/zh/spl.iterators.php