RecursiveIteratorIterator
如何工作?
PHP手册没有太多文档记录或解释。 IteratorIterator
和RecursiveIteratorIterator
有什么区别?
最佳答案
RecursiveIteratorIterator
是实现 Iterator
的具体tree traversal。它使程序员能够遍历实现RecursiveIterator
接口(interface)的容器对象,有关迭代器的一般原理,类型,语义和模式,请参见Iterator in Wikipedia。
与 IteratorIterator
不同,后者是具体的Iterator
,它以线性顺序实现对象遍历(默认情况下在其构造函数中接受任何类型的 Traversable
),RecursiveIteratorIterator
允许循环遍历对象树中的所有节点,并且构造函数采用RecursiveIterator
。
简而言之:RecursiveIteratorIterator
允许您遍历树,IteratorIterator
允许您遍历列表。我很快在下面的一些代码示例中对此进行了展示。
从技术上讲,这可以通过遍历节点的所有子节点(如果有)来打破线性关系而起作用。这是可能的,因为根据定义,节点的所有子节点还是RecursiveIterator
。然后,顶层Iterator
在内部按其深度堆叠不同的RecursiveIterator
,并保留指向当前 Activity 子Iterator
的指针以进行遍历。
这允许访问树的所有节点。
基本原理与IteratorIterator
相同:接口(interface)指定迭代类型,基本迭代器类是这些语义的实现。与下面的示例进行比较,对于使用foreach
进行线性循环,通常除非您需要定义新的Iterator
(例如,当某些具体类型本身未实现Traversable
时),否则您通常不会过多地考虑实现细节。
对于递归遍历-除非不使用已经具有递归遍历迭代的预定义Traversal
-通常,您需要实例化现有的RecursiveIteratorIterator
迭代,甚至编写自己的Traversable
递归遍历迭代,以具有这种类型的遍历迭代与foreach
。
简而言之,技术差异:
IteratorIterator
采用任何Traversable
进行线性遍历,但RecursiveIteratorIterator
需要更具体的RecursiveIterator
遍历树。 IteratorIterator
通过Iterator
公开其主要getInnerIerator()
,RecursiveIteratorIterator
仅通过该方法提供当前 Activity 的子Iterator
。 IteratorIterator
完全不了解 parent 或 child ,但RecursiveIteratorIterator
也知道如何获取和遍历 child 。 IteratorIterator
不需要堆栈的迭代器,RecursiveIteratorIterator
具有这样的堆栈,并且知道 Activity 的子迭代器。 IteratorIterator
由于线性而有其顺序,没有选择余地,RecursiveIteratorIterator
可以选择进一步遍历,并且需要针对每个节点进行决定(通过mode per RecursiveIteratorIterator
决定)。 RecursiveIteratorIterator
比IteratorIterator
具有更多的方法。 总结一下:
RecursiveIterator
是一种具体的迭代类型(在树上循环),可在其自己的迭代器上工作,即RecursiveIterator
。这与IteratorIerator
的基本原理相同,但是迭代类型不同(线性顺序)。理想情况下,您也可以创建自己的集合。唯一需要做的是,您的迭代器实现
Traversable
,可以通过Iterator
或IteratorAggregate
来实现。然后,您可以将其与foreach
一起使用。例如,某种三叉树遍历递归迭代对象以及容器对象的相应迭代接口(interface)。让我们来看一些不是那么抽象的现实示例。在接口(interface),具体的迭代器,容器对象和迭代语义之间,这可能不是一个坏主意。
以目录列表为例。考虑您在磁盘上有以下文件和目录树:
线性顺序的迭代器仅遍历顶级文件夹和文件(单个目录列表),而递归迭代器也遍历子文件夹并列出所有文件夹和文件(目录列表及其子目录的列表):
Non-Recursive Recursive
============= =========
[tree] [tree]
├ dirA ├ dirA
└ fileA │ ├ dirB
│ │ └ fileD
│ ├ fileB
│ └ fileC
└ fileA
您可以轻松地将它与
IteratorIterator
进行比较,后者无需遍历目录树即可递归。以及可以递归到树中的RecursiveIteratorIterator
(如递归列表所示)。首先,一个带有
DirectoryIterator
的非常基本的示例实现了 Traversable
,它允许 foreach
对其进行迭代:$path = 'tree';
$dir = new DirectoryIterator($path);
echo "[$path]\n";
foreach ($dir as $file) {
echo " ├ $file\n";
}
那么上面的目录结构的示例输出是:
[tree]
├ .
├ ..
├ dirA
├ fileA
如您所见,这尚未使用
IteratorIterator
或RecursiveIteratorIterator
。相反,它仅使用在foreach
接口(interface)上运行的Traversable
。由于默认情况下
foreach
仅知道名为线性顺序的迭代类型,因此我们可能需要显式指定迭代类型。乍看起来,它似乎太冗长了,但是出于演示目的(并在以后使RecursiveIteratorIterator
变得与众不同),让我们指定线性迭代类型,为目录列表显式指定IteratorIterator
迭代类型:$files = new IteratorIterator($dir);
echo "[$path]\n";
foreach ($files as $file) {
echo " ├ $file\n";
}
这个示例与第一个示例几乎相同,不同之处在于
$files
现在是IteratorIterator
Traversable
的$dir
迭代类型:$files = new IteratorIterator($dir);
像往常一样,迭代的 Action 是由
foreach
执行的:foreach ($files as $file) {
输出完全相同。那么有什么不同呢?
foreach
中使用的对象不同。在第一个示例中,它是DirectoryIterator
;在第二个示例中,它是IteratorIterator
。这显示了迭代器具有的灵活性:您可以将它们彼此替换,foreach
中的代码将继续按预期工作。让我们开始获取整个列表,包括子目录。
现在我们已经指定了迭代类型,让我们考虑将其更改为另一种迭代类型。
我们知道我们现在需要遍历整个树,而不仅仅是第一层。要使用简单的
foreach
进行处理,我们需要使用其他类型的迭代器: RecursiveIteratorIterator
。而且那只能迭代具有 RecursiveIterator
interface的容器对象。该接口(interface)是契约(Contract)。任何实现它的类都可以与
RecursiveIteratorIterator
一起使用。此类的一个示例是 RecursiveDirectoryIterator
,类似于DirectoryIterator
的递归变体。让我们看第一个代码示例,然后再写其他带有I字的句子:
$dir = new RecursiveDirectoryIterator($path);
echo "[$path]\n";
foreach ($dir as $file) {
echo " ├ $file\n";
}
第三个示例与第一个示例几乎相同,但是它创建了一些不同的输出:
[tree]
├ tree\.
├ tree\..
├ tree\dirA
├ tree\fileA
好的,没什么不同,文件名现在在前面包含路径名,但是其余的看起来也差不多。
如示例所示,即使目录对象已经实现了
RecursiveIterator
接口(interface),这仍然不足以使foreach
遍历整个目录树。这是RecursiveIteratorIterator
起作用的地方。示例4显示了如何:$files = new RecursiveIteratorIterator($dir);
echo "[$path]\n";
foreach ($files as $file) {
echo " ├ $file\n";
}
使用
RecursiveIteratorIterator
而不是仅使用先前的$dir
对象将使foreach
以递归方式遍历所有文件和目录。然后列出所有文件,因为现在已经指定了对象迭代的类型:[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
这应该已经证明了平面遍历和树遍历之间的区别。
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
当然,这不会赢得选美比赛,但是它表明,使用递归迭代器,不仅可以得到键和值的线性顺序,还可以获得更多信息。甚至
foreach
也只能表达这种线性度,访问迭代器本身可以获取更多信息。与元信息类似,也有不同的方式可能遍历树并因此对输出进行排序。这是Mode of the
RecursiveIteratorIterator
,可以使用构造函数进行设置。下一个示例将告诉
RecursiveDirectoryIterator
删除点条目(.
和..
),因为我们不需要它们。但是,递归模式也将更改为在子元素(子目录中的文件和子子目录)之前先使用父元素(子目录)(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
因此,对于目录示例,递归模式控制返回什么以及何时返回树中的分支或叶子:
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
当您将其与标准遍历进行比较时,所有这些都不可用。因此,当您需要绕头绕行时,递归迭代会稍微复杂一点,但是它易于使用,因为它的行为就像迭代器一样,您将其放入
foreach
中并完成。我认为这些例子足以解决一个问题。您可以在此要点中找到完整的源代码以及显示漂亮的ascii树的示例:https://gist.github.com/3599532
示例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
结合使用时,它会显示整个路径名,而不仅仅是文件名。其余的看起来不错。这是因为文件名是由SplFileInfo
生成的。那些应该改为显示为基本名称。所需的输出如下:/// Solved ///
[tree]
├ dirA
│ ├ dirB
│ │ └ fileD
│ ├ fileB
│ └ fileC
└ fileA
创建一个可以与
RecursiveTreeIterator
而不是RecursiveDirectoryIterator
一起使用的装饰器类。它应该提供当前SplFileInfo
的基本名称,而不是路径名称。最终的代码片段如下所示:$lines = new RecursiveTreeIterator(
new DiyRecursiveDecorator($dir)
);
$unicodeTreePrefix($lines);
echo "[$path]\n", implode("\n", iterator_to_array($lines));
这些包括
$unicodeTreePrefix
的片段是附录中“要点”的一部分:自己动手:逐行制作RecursiveTreeIterator
。