RecursiveIteratorIterator如何在PHP中工作?
RecursiveIteratorIterator
如何工作?
PHP手册没有太多的文档或解释。 IteratorIterator
和RecursiveIteratorIterator
什么区别?
RecursiveIteratorIterator
是一个实现树遍历的具体Iterator
。 它使程序员能够遍历实现RecursiveIterator
接口的容器对象,请参阅Wikipedia中的迭代器 ,了解迭代器的一般原理,types,语义和模式。
与IteratorIterator
不同, IteratorIterator
是一个以线性顺序实现对象遍历的具体Iterator
(并且默认接受其构造函数中的任何types的Traversable
), RecursiveIteratorIterator
允许遍历对象的有序树中的所有节点,其构造函数使用RecursiveIterator
。
简而言之: RecursiveIteratorIterator
允许你遍历树, IteratorIterator
允许你遍历一个列表。 我将在下面展示一些代码示例。
从技术上讲,这是通过遍历所有节点的子节点(如果有的话)来突破线性的。 这是可能的,因为根据定义,节点的所有子节点都是RecursiveIterator
。 顶层Iterator
然后在内部堆栈不同的RecursiveIterator
的深度,并保持一个指向当前活动的子Iterator
的遍历指针。
这允许访问树的所有节点。
底层原则与IteratorIterator
相同:接口指定迭代的types,基础迭代器类是这些语义的实现。 比较下面的例子,对于foreach
线性循环,除非需要定义一个新的Iterator
(例如某些具体types本身没有实现Traversable
),否则通常不会考虑实现的细节。
对于recursion遍历 – 除非不使用已经有recursion遍历迭代的预定义Traversal
,否则通常需要实例化现有的RecursiveIteratorIterator
迭代,或者甚至写一个自己遍历的recursion遍历迭代来进行这种types的遍历迭代与foreach
。
提示:你可能没有实现这一个,也可能不是你自己的,所以这可能是你实际体验到他们之间差异的一些值得做的事情。 在答案的最后,你会发现一个DIYbuild议。
技术差异简而言之:
- 虽然
IteratorIterator
任何Traversable
进行线性遍历,但RecursiveIteratorIterator
需要更具体的RecursiveIterator
来遍历树。 - 在
IteratorIterator
通过getInnerIerator()
公开其主Iterator
getInnerIerator()
,RecursiveIteratorIterator
仅通过该方法提供当前活动的子Iterator
。 - 虽然
IteratorIterator
完全不知道父类或子类,RecursiveIteratorIterator
也知道如何获取和遍历子元素。 -
IteratorIterator
不需要迭代器的堆栈,RecursiveIteratorIterator
有这样一个堆栈并知道活动的子迭代器。 - 在
IteratorIterator
由于线性而无法select的情况下,RecursiveIteratorIterator
可以select进一步的遍历,并且需要为每个节点决定(通过RecursiveIteratorIterator
通过模式决定)。 -
RecursiveIteratorIterator
比IteratorIterator
有更多的方法。
总结一下: RecursiveIterator
是一个迭代的具体types(在树上循环),它在自己的迭代器上工作,即RecursiveIterator
。 这与IteratorIerator
基本原理是一样的,但是迭代的types是不同的(线性顺序)。
理想情况下,您也可以创build自己的设置。 唯一需要的是你的迭代器实现了可以通过Iterator
或者IteratorAggregate
实现的Traversable
。 那么你可以用foreach
来使用它。 例如某种三元树遍历recursion迭代对象,以及容器对象的相应迭代接口。
让我们回顾一些不是那么抽象的现实生活中的例子。 在接口,具体迭代器,容器对象和迭代语义之间,这可能不是一个坏主意。
以目录列表为例。 考虑你在磁盘上有下面的文件和目录树:
虽然具有线性顺序的迭代器只是遍历顶层文件夹和文件(单个目录列表),但recursion迭代器也遍历子文件夹,并列出所有文件夹和文件(列出其子目录列表的目录):
Non-Recursive Recursive ============= ========= [tree] [tree] ├ dirA ├ dirA └ fileA │ ├ dirB │ │ └ fileD │ ├ fileB │ └ fileC └ fileA
您可以轻松地将其与IteratorIterator
进行比较,该IteratorIterator
不遍历目录树。 RecursiveIteratorIterator
可以遍历到recursion列表显示的树中。
首先用DirectoryIterator
实现Traversable
一个非常基本的例子,它允许foreach
遍历它:
$path = 'tree'; $dir = new DirectoryIterator($path); echo "[$path]\n"; foreach ($dir as $file) { echo " ├ $file\n"; }
上面的目录结构的示例性输出是:
[tree] ├ . ├ .. ├ dirA ├ fileA
正如你所看到的,这还没有使用IteratorIterator
或者RecursiveIteratorIterator
。 相反,它只是使用在Traversable
接口上运行的foreach
。
由于默认情况下foreach
只知道名为线性顺序的迭代types,所以我们可能要明确指定迭代的types。 乍一看,它可能看起来太冗长了,但是为了演示的目的(并且为了使RecursiveIteratorIterator
与以后更加明显不同),可以指定迭代的线性types,明确指定目录列表的IteratorIterator
types:
$files = new IteratorIterator($dir); echo "[$path]\n"; foreach ($files as $file) { echo " ├ $file\n"; }
这个例子和第一个例子几乎是一样的,区别在于$files
现在是Traversable
$dir
的IteratorIterator
types:
$files = new IteratorIterator($dir);
像往常一样,迭代的行为是由foreach
执行的:
foreach ($files as $file) {
输出是完全一样的。 那么有什么不同? 在foreach
使用的对象是不同的。 在第一个例子中,它是第二个例子中的DirectoryIterator
,它是IteratorIterator
。 这显示了迭代器具有的灵活性:您可以相互replace它们, foreach
的代码只是继续按预期工作。
让我们开始获取整个列表,包括子目录。
正如我们现在已经指定了迭代的types,让我们考虑将其更改为另一种types的迭代。
我们知道我们现在需要遍历整棵树,而不仅仅是第一层。 要使用简单的foreach
工作,我们需要一个不同types的迭代器: RecursiveIteratorIterator
。 而且只能迭代具有RecursiveIterator
接口的容器对象。
界面是一个合同。 任何实现它的类都可以和RecursiveIteratorIterator
一起使用。 这样的类的一个例子是RecursiveDirectoryIterator
,它就像DirectoryIterator
的recursion变体。
让我们看看第一个代码示例,然后用I字写任何其他句子:
$dir = new RecursiveDirectoryIterator($path); echo "[$path]\n"; foreach ($dir as $file) { echo " ├ $file\n"; }
这第三个例子与第一个例子几乎是一样的,但是它创build了一些不同的输出:
[tree] ├ tree\. ├ tree\.. ├ tree\dirA ├ tree\fileA
好吧,没有什么不同,文件名现在包含前面的path名,但其余的看起来也相似。
如示例所示,即使目录对象已经使RecursiveIterator
接口变得模糊,这还不足以使foreach
遍历整个目录树。 这是RecursiveIteratorIterator
开始执行的地方。 示例4显示了如何:
$files = new RecursiveIteratorIterator($dir); echo "[$path]\n"; foreach ($files as $file) { echo " ├ $file\n"; }
使用RecursiveIteratorIterator
而不是前面的$dir
对象将使foreach
以recursion方式遍历所有文件和目录。 这会列出所有文件,因为现在已经指定了对象迭代的types:
[tree] ├ tree\. ├ tree\.. ├ tree\dirA\. ├ tree\dirA\.. ├ tree\dirA\dirB\. ├ tree\dirA\dirB\.. ├ tree\dirA\dirB\fileD ├ tree\dirA\fileB ├ tree\dirA\fileC ├ tree\fileA
这应该已经certificate了平面和树遍历之间的区别。 RecursiveIteratorIterator
能够遍历任何树状结构作为元素列表。 因为有更多的信息(比如迭代当前所在的级别),可以在遍历迭代器对象时访问迭代器对象,例如缩进输出:
echo "[$path]\n"; foreach ($files as $file) { $indent = str_repeat(' ', $files->getDepth()); echo $indent, " ├ $file\n"; }
和例5的输出:
[tree] ├ tree\. ├ tree\.. ├ tree\dirA\. ├ tree\dirA\.. ├ tree\dirA\dirB\. ├ tree\dirA\dirB\.. ├ tree\dirA\dirB\fileD ├ tree\dirA\fileB ├ tree\dirA\fileC ├ tree\fileA
当然,这并不能赢得选美大赛,但是它表明,使用recursion迭代器可以获得更多的信息,而不仅仅是关键字和值的线性顺序。 即使是foreach
也只能expression这种线性,访问迭代器本身可以获得更多的信息。
类似于元信息,也有不同的方法可能如何遍历树并因此命令输出。 这是RecursiveIteratorIterator
的模式 ,可以使用构造函数进行设置。
下一个示例将告诉RecursiveDirectoryIterator
删除点条目( .
和..
),因为我们不需要它们。 但是,recursion模式也将被改变,以便在子(子目录中的文件和子子目录)之前首先获取父元素(子目录)( SELF_FIRST
):
$dir = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS); $files = new RecursiveIteratorIterator($dir, RecursiveIteratorIterator::SELF_FIRST); echo "[$path]\n"; foreach ($files as $file) { $indent = str_repeat(' ', $files->getDepth()); echo $indent, " ├ $file\n"; }
现在,输出显示正确列出的子目录条目,如果与以前不存在的输出进行比较:
[tree] ├ tree\dirA ├ tree\dirA\dirB ├ tree\dirA\dirB\fileD ├ tree\dirA\fileB ├ tree\dirA\fileC ├ tree\fileA
因此,recursion模式控制着什么以及什么时候返回树中的布尔或叶子,例如:
-
LEAVES_ONLY
(默认):只列出文件,没有目录。 -
SELF_FIRST
(上面):列出目录,然后是那里的文件。 -
CHILD_FIRST
(没有例子):首先列出子目录中的文件,然后是目录。
例5的输出与另外两种模式:
LEAVES_ONLY CHILD_FIRST [tree] [tree] ├ tree\dirA\dirB\fileD ├ tree\dirA\dirB\fileD ├ tree\dirA\fileB ├ tree\dirA\dirB ├ tree\dirA\fileC ├ tree\dirA\fileB ├ tree\fileA ├ tree\dirA\fileC ├ tree\dirA ├ tree\fileA
当你将其与标准遍历进行比较时,所有这些东西都不可用。 因此,当你需要围绕它进行recursion迭代时,recursion迭代会更复杂一些,但是它很容易使用,因为它的行为就像一个迭代器,把它放到foreach
完成。
我认为这些就是一个答案的例子。 你可以find完整的源代码以及一个例子来显示漂亮的ascii-tree在这个要点: https : //gist.github.com/3599532
自己动手:逐行制作
RecursiveTreeIterator
工作线。
例5展示了有关迭代器状态可用的元信息。 但是,这是在 foreach
迭代中有目的地演示的。 在现实生活中,这自然属于RecursiveIterator
内部。
一个更好的例子是RecursiveTreeIterator
,它负责缩进,前缀等。 看下面的代码片段:
$dir = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS); $lines = new RecursiveTreeIterator($dir); $unicodeTreePrefix($lines); echo "[$path]\n", implode("\n", iterator_to_array($lines));
RecursiveTreeIterator
的目的是逐行工作,输出是相当简单的一个小问题:
[tree] ├ tree\dirA │ ├ tree\dirA\dirB │ │ └ tree\dirA\dirB\fileD │ ├ tree\dirA\fileB │ └ tree\dirA\fileC └ tree\fileA
当与RecursiveDirectoryIterator
结合使用时,它将显示整个path名,而不仅仅是文件名。 其余的看起来不错。 这是因为文件名是由SplFileInfo
生成的。 这些应该显示为基本名称。 期望的输出如下:
/// Solved /// [tree] ├ dirA │ ├ dirB │ │ └ fileD │ ├ fileB │ └ fileC └ fileA
创build一个可以与RecursiveTreeIterator
而不是RecursiveDirectoryIterator
一起使用的装饰器类。 它应该提供当前SplFileInfo
的基名而不是path名。 最终的代码片段可能看起来像这样:
$lines = new RecursiveTreeIterator( new DiyRecursiveDecorator($dir) ); $unicodeTreePrefix($lines); echo "[$path]\n", implode("\n", iterator_to_array($lines));
这些片段包括$unicodeTreePrefix
是附录中的要点的一部分:自己动手:使RecursiveTreeIterator
$unicodeTreePrefix
工作。 。
IteratorIterator
和RecursiveIteratorIterator
什么区别?
为了理解这两个迭代器之间的区别,首先必须了解一些关于使用的命名约定和我们所说的“recursion”迭代器的含义。
recursion和非recursion迭代器
PHP有非recursion迭代器,比如ArrayIterator
和FilesystemIterator
。 还有“recursion”迭代器,如RecursiveArrayIterator
和RecursiveDirectoryIterator
。 后者有办法让他们钻进去,前者不行。
当这些迭代器的实例独立循环时,即使循环遍历嵌套数组或子目录的目录,值也只能来自“顶”级。
recursion迭代器实现recursion行为(通过hasChildren()
, getChildren()
),但不要利用它。
将recursion迭代器看作“recursion”迭代器可能会更好,它们有recursion迭代的能力 ,但只是遍历其中一个类的实例就不会这样做。 要利用recursion行为,请继续阅读。
RecursiveIteratorIterator
这是RecursiveIteratorIterator
进来玩的地方。 它具有如何以这样的方式调用“recursion”迭代器的知识,以便以正常的,平坦的循环向下钻入结构。 它将recursion行为付诸行动。 它基本上完成了遍历迭代器中每个值的工作,看看是否有“子”进行recursion,并进入和退出这些子集。 你将一个RecursiveIteratorIterator
的实例粘贴到一个foreach中,然后深入到这个结构中,这样你就不必这样做了。
如果不使用RecursiveIteratorIterator
则必须编写自己的recursion循环来利用recursion行为,使用getChildren()
检查“recursion”迭代器的hasChildren()
getChildren()
。
所以这是对RecursiveIteratorIterator
的简要概述,它与IteratorIterator
有什么不同呢? 那么你基本上就是问一个问题:小猫和树有什么不同? 只是因为两者出现在相同的百科全书(或手册,迭代器)并不意味着你应该混淆在二者之间。
IteratorIterator
IteratorIterator
的工作是获取任何Traversable
对象,并将其包装为满足Iterator
接口。 这样做的一个用途就是能够在非迭代器对象上应用特定于迭代器的行为。
举一个实际的例子, DatePeriod
类是Traversable
但不是Iterator
。 因此,我们可以用foreach()
来foreach()
它的值,但是不能做其他我们通常用迭代器来做的事情,比如过滤。
任务 :在接下来的四周的周一,周三和周五。
是的,通过在DatePeriod
使用foreach
并在循环中使用if()
,这是微不足道的。 但这不是这个例子的重点!
$period = new DatePeriod(new DateTime, new DateInterval('P1D'), 28); $dates = new CallbackFilterIterator($period, function ($date) { return in_array($date->format('l'), array('Monday', 'Wednesday', 'Friday')); }); foreach ($dates as $date) { … }
上面的代码片段将不起作用,因为CallbackFilterIterator
需要一个实现Iterator
接口的类的实例,而DatePeriod
则不需要。 但是,由于它是可Traversable
我们可以通过使用IteratorIterator
轻松满足这个要求。
$period = new IteratorIterator(new DatePeriod(…));
正如你所看到的,这对于遍历迭代器类和recursion没有任何作用,其中存在着IteratorIterator
和RecursiveIteratorIterator
的区别。
概要
RecursiveIteraratorIterator
用于迭代RecursiveIterator
(“recursible”迭代器),利用可用的recursion行为。
IteratorIterator
用于将Iterator
行为应用于非迭代器Traversable
对象。
RecursiveDirectoryIterator它显示整个path名,而不仅仅是文件名。 其余的看起来不错。 这是因为文件名是由SplFileInfo生成的。 这些应该显示为基本名称。 期望的输出如下:
$path =__DIR__; $dir = new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS); $files = new RecursiveIteratorIterator($dir,RecursiveIteratorIterator::SELF_FIRST); while ($files->valid()) { $file = $files->current(); $filename = $file->getFilename(); $deep = $files->getDepth(); $indent = str_repeat('│ ', $deep); $files->next(); $valid = $files->valid(); if ($valid and ($files->getDepth() - 1 == $deep or $files->getDepth() == $deep)) { echo $indent, "├ $filename\n"; } else { echo $indent, "└ $filename\n"; } }
输出:
tree ├ dirA │ ├ dirB │ │ └ fileD │ ├ fileB │ └ fileC └ fileA
当与iterator_to_array()
, RecursiveIteratorIterator
将recursion地遍历数组以查找所有值。 这意味着它将使原始数组扁平化。
IteratorIterator
将保持原始的层次结构。
这个例子会清楚地显示出你的区别:
$array = array( 'ford', 'model' => 'F150', 'color' => 'blue', 'options' => array('radio' => 'satellite') ); $recursiveIterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($array)); var_dump(iterator_to_array($recursiveIterator, true)); $iterator = new IteratorIterator(new ArrayIterator($array)); var_dump(iterator_to_array($iterator,true));