RecursiveIteratorIterator
如何运作?
PHP手册没有任何记录或解释。 IteratorIterator
和RecursiveIteratorIterator
之间有什么区别?
答案 0 :(得分:225)
RecursiveIteratorIterator
是具体Iterator
实施tree traversal。它使程序员能够遍历实现RecursiveIterator
接口的容器对象,请参阅Iterator in Wikipedia了解迭代器的一般原理,类型,语义和模式。
与IteratorIterator
不同,Traversable
是一个具体的Iterator
实现对象遍历的线性顺序(默认情况下在其构造函数中接受任何类型的mode per RecursiveIteratorIterator
),RecursiveIteratorIterator
允许循环遍历对象的有序树中的所有节点,其构造函数采用RecursiveIterator
。
简而言之:RecursiveIteratorIterator
允许您循环遍历树,IteratorIterator
允许您循环遍历列表。我将在下面展示一些代码示例。
从技术上讲,这可以通过遍历所有节点来消除线性度。孩子(如果有的话)。这是可能的,因为根据定义,节点的所有子节点都是RecursiveIterator
。然后,toplevel Iterator
在内部按不同的深度堆叠不同的RecursiveIterator
,并保留指向当前活动子Iterator
的指针以进行遍历。
这允许访问树的所有节点。
基本原则与IteratorIterator
相同:接口指定迭代的类型,基本迭代器类是这些语义的实现。与下面的示例相比,对于foreach
的线性循环,除非需要定义新的Iterator
,否则通常不会考虑实现细节(例如,当某些具体类型本身未实现{{1}时}})。
对于递归遍历 - 除非您不使用已经具有递归遍历迭代的预定义Traversable
- 您通常需要来实例化现有的Traversal
迭代甚至编写一个递归遍历迭代,它是您自己的RecursiveIteratorIterator
,可以使用Traversable
进行这种类型的遍历迭代。
提示:您可能没有实现自己的那个,所以这可能是您在实际体验中所带来的差异所值得的。你会在答案的最后找到一个DIY建议。
技术差异简称:
foreach
需要IteratorIterator
进行线性遍历,Traversable
需要更具体的RecursiveIteratorIterator
来循环遍历树。RecursiveIterator
通过IteratorIterator
公开其主Iterator
的位置,getInnerIerator()
仅通过该方法提供当前有效的子RecursiveIteratorIterator
。Iterator
完全不了解父母或孩子之类的内容,但IteratorIterator
知道如何获取和遍历儿童。RecursiveIteratorIterator
不需要堆栈的迭代器,IteratorIterator
具有这样的堆栈并且知道活动的子迭代器。RecursiveIteratorIterator
由于线性而没有选择的顺序,IteratorIterator
可以选择进一步遍历,需要根据每个节点决定(通过DirectoryIterator
决定)。RecursiveIteratorIterator
的方法多于RecursiveIteratorIterator
。总结一下:IteratorIterator
是一种具体的迭代类型(在树上循环),它在自己的迭代器上工作,即RecursiveIterator
。这与RecursiveIterator
的基本原理相同,但迭代的类型不同(线性顺序)。
理想情况下,您也可以创建自己的套装。唯一需要的是你的迭代器通过IteratorIerator
或Traversable
实现Iterator
。然后,您可以将其与IteratorAggregate
一起使用。例如,某种三元树遍历递归迭代对象以及容器对象的相应迭代接口。
让我们回顾一些不那么抽象的现实例子。在接口,具体迭代器,容器对象和迭代语义之间,这可能不是一个坏主意。
以目录列表为例。假设您在磁盘上有以下文件和目录树:
虽然具有线性顺序的迭代器只遍历顶层文件夹和文件(单个目录列表),但递归迭代器也会遍历子文件夹并列出所有文件夹和文件(包含其子目录列表的目录列表): / p>
foreach
您可以轻松地将其与Non-Recursive Recursive
============= =========
[tree] [tree]
├ dirA ├ dirA
└ fileA │ ├ dirB
│ │ └ fileD
│ ├ fileB
│ └ fileC
└ fileA
进行比较,后者不会遍历目录树。并且IteratorIterator
可以遍历到树中,如递归列表所示。
首先是一个非常基本的示例,Traversable
实现foreach
,允许RecursiveIteratorIterator
迭代:
RecursiveIteratorIterator
上面目录结构的示例输出是:
$path = 'tree';
$dir = new DirectoryIterator($path);
echo "[$path]\n";
foreach ($dir as $file) {
echo " ├ $file\n";
}
如您所见,尚未使用[tree]
├ .
├ ..
├ dirA
├ fileA
或IteratorIterator
。相反,它只是使用RecursiveIteratorIterator
界面上运行的foreach
。
默认情况下,Traversable
只知道名为线性顺序的迭代类型,我们可能希望明确指定迭代类型。乍一看,它看起来似乎太冗长了,但是为了演示目的(并且为了使foreach
稍后更加明显),让我们指定迭代的线性类型,明确指定RecursiveIteratorIterator
类型的迭代。目录列表:
IteratorIterator
此示例几乎与第一个相同,不同之处在于$files = new IteratorIterator($dir);
echo "[$path]\n";
foreach ($files as $file) {
echo " ├ $file\n";
}
现在是$files
IteratorIterator
类型的迭代Traversable
{{1} }:
$dir
与往常一样,迭代行为由$files = new IteratorIterator($dir);
:
foreach
输出完全相同。那有什么不同呢? foreach ($files as $file) {
中使用的对象不同。在第一个示例中,在第二个示例中它是foreach
,它是DirectoryIterator
。这显示了迭代器具有的灵活性:您可以相互替换它们,IteratorIterator
中的代码可以继续按预期工作。
让我们开始获取整个列表,包括子目录。
由于我们现在已经指定了迭代的类型,因此我们考虑将其更改为另一种迭代类型。
我们知道我们现在需要遍历整个树,而不仅仅是第一层。要使用简单的foreach
进行工作,我们需要一种不同类型的迭代器:RecursiveIterator
interface。而且只能迭代具有RecursiveDirectoryIterator
。
界面是合约。任何实现它的类都可以与foreach
一起使用。这类的一个例子是Mode of the RecursiveIteratorIterator
,它类似于RecursiveIteratorIterator
的递归变体。
让我们在用I-word编写任何其他句子之前看第一个代码示例:
DirectoryIterator
第三个例子几乎与第一个例子相同,但是它创建了一些不同的输出:
$dir = new RecursiveDirectoryIterator($path);
echo "[$path]\n";
foreach ($dir as $file) {
echo " ├ $file\n";
}
好的,没有那么不同,文件名现在包含前面的路径名,但其余的看起来也相似。
如示例所示,即使目录对象已经嵌入[tree]
├ tree\.
├ tree\..
├ tree\dirA
├ tree\fileA
接口,这还不足以使RecursiveIterator
遍历整个目录树。这是foreach
开始行动的地方。 示例4 显示了如何:
RecursiveIteratorIterator
使用$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
示例5的输出:
echo "[$path]\n";
foreach ($files as $file) {
$indent = str_repeat(' ', $files->getDepth());
echo $indent, " ├ $file\n";
}
当然这不会赢得选美比赛,但它表明,使用递归迭代器可以获得更多信息,而不仅仅是键和值的线性顺序。即使[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
只能表达这种线性,访问迭代器本身也可以获得更多信息。
与元信息类似,如何遍历树并因此对输出进行排序也有不同的方法。这是https://gist.github.com/3599532,可以使用构造函数进行设置。
下一个示例将告诉foreach
删除点条目(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";
}
因此,对于目录示例,递归模式控制返回树中的brach或leaf的内容和时间:
[tree]
├ tree\dirA
├ tree\dirA\dirB
├ tree\dirA\dirB\fileD
├ tree\dirA\fileB
├ tree\dirA\fileC
├ tree\fileA
(默认值):仅列出文件,无目录。LEAVES_ONLY
(上图):列出目录,然后是那里的文件。SELF_FIRST
(没有例子):首先列出子目录中的文件,然后列出目录。使用另外两种模式输出示例5 :
CHILD_FIRST
当您将其与标准遍历进行比较时,所有这些都无法使用。因此,当你需要绕过它时,递归迭代有点复杂,但它易于使用,因为它的行为就像迭代器一样,你将它放入 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
并完成。
我认为这些是一个答案的足够例子。您可以在这个要点中找到完整的源代码以及显示漂亮的ascii-trees的示例:RecursiveTreeIterator
自己动手:逐行制作
foreach
工作线。
示例5 表明存在关于迭代器状态的元信息。但是,这是有目的地在 RecursiveTreeIterator
迭代中演示的。在现实生活中,这自然属于foreach
。
更好的例子是{{3}},它负责缩进,前缀等。请参阅以下代码片段:
RecursiveIterator
$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
工作。。
答案 1 :(得分:30)
IteratorIterator
和RecursiveIteratorIterator
有什么区别?
要理解这两个迭代器之间的区别,首先必须先了解一下使用的命名约定以及“递归”迭代器的含义。
PHP具有非“递归”迭代器,例如ArrayIterator
和FilesystemIterator
。还有“递归”迭代器,例如RecursiveArrayIterator
和RecursiveDirectoryIterator
。后者有一些方法可以将它们钻进去,而前者则没有。
当这些迭代器的实例自行循环时,即使是递归的迭代器,这些值也只来自“顶层”,即使循环遍历带有子目录的嵌套数组或目录。
递归迭代器实现递归行为(通过hasChildren()
,getChildren()
)但不利用它。
将递归迭代器视为“递归”迭代器可能更好,它们可以递归迭代能力,但只是迭代这些类之一的实例就不会这样做。要利用递归行为,请继续阅读。
这是RecursiveIteratorIterator
进场的地方。它具有如何调用“递归”迭代器的知识,以便在正常的平坦循环中向下钻取结构。它将递归行为付诸行动。它主要完成跨越迭代器中每个值的工作,查看是否有“子”进入或不进入,以及进入和退出这些子集合。您将RecursiveIteratorIterator
的实例粘贴到foreach中,然后 it 潜入结构中,以便您不必这样做。
如果未使用RecursiveIteratorIterator
,则必须编写自己的递归循环以利用递归行为,检查“递归”迭代器的hasChildren()
并使用getChildren()
。< / p>
这是RecursiveIteratorIterator
的简要概述,它与IteratorIterator
有什么不同?好吧,你基本上问的是同样的问题小猫和树之间的区别是什么?因为两者都出现在同一个百科全书(或手册,对于迭代器)不意味着你应该在两者之间感到困惑。
IteratorIterator
的工作是获取任何Traversable
对象,并将其包装以使其满足Iterator
接口。这样做的用途是能够在非迭代器对象上应用特定于迭代器的行为。
举一个实际的例子,DatePeriod
类是Traversable
,而不是Iterator
。因此,我们可以使用foreach()
循环其值,但不能执行我们通常使用迭代器进行的其他操作,例如过滤。
任务:在接下来的四周的周一,周三和周五进行。
是的,foreach
- DatePeriod
并在循环中使用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(…));
正如你所看到的,迭代迭代器类和递归都没有没有,而IteratorIterator
和RecursiveIteratorIterator
之间存在差异。
RecursiveIteraratorIterator
用于迭代RecursiveIterator
(“递归”迭代器),利用可用的递归行为。
IteratorIterator
用于将Iterator
行为应用于非迭代器,Traversable
对象。
答案 2 :(得分:0)
RecursiveDirectoryIterator它显示整个路径名而不仅仅是文件名。其余的看起来不错。这是因为文件名是由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
答案 3 :(得分:-1)
与iterator_to_array()
一起使用时,RecursiveIteratorIterator
将递归遍历数组以查找所有值。这意味着它会使原始数组变平。
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));