RecursiveIteratorIterator是如何工作的?
PHP手册中没有很多文档或解释。IteratorIterator和RecursiveIteratorIterator之间有什么区别?
RecursiveIteratorIterator
是一个实现树遍历的具体Iterator
。它使程序员能够遍历实现RecursiveIterator
接口的容器对象,有关迭代器的一般原则、类型、语义和模式,请参见维基百科中的迭代器。
与IteratorIterator
不同,它是一个按线性顺序实现对象遍历的具体Iterator
实现(默认情况下,在其构造函数中接受任何类型的Traversable
),RecursiveIteratorIterator
允许循环遍历对象的有序树中的所有节点,并且它的构造函数需要一个RecursiveIterator
。
RecursiveIteratorIterator
允许您循环遍历树,IteratorIterator
允许您循环遍历列表。我将在下面用一些代码示例展示。
从技术上讲,这是通过遍历节点的所有子节点(如果有)来打破线性的方式实现的。这是可能的,因为根据定义,节点的所有子节点再次都是RecursiveIterator
。然后,顶层的Iterator
通过其深度内部堆叠不同的RecursiveIterator
并保持指向当前活动子Iterator
的指针进行遍历。
这允许访问树的所有节点。
基本原理与IteratorIterator
相同:接口指定迭代类型,基本迭代器类是这些语义的实现。请参阅下面的示例,对于使用foreach
进行线性循环,通常不需要考虑实现细节,除非需要定义新的Iterator
(例如,当某个具体类型本身不实现Traversable
时)。
对于递归遍历 - 除非您不使用已经具有递归遍历迭代的预定义Traversal
,否则通常需要实例化现有的RecursiveIteratorIterator
迭代,甚至编写一个自己的Traversable
的递归遍历迭代,以便在foreach
中进行此类型的遍历迭代。
提示:您可能没有实现其中任何一个,因此这可能是值得做的事情,以了解它们之间的区别。您可以在答案末尾找到一个DIY建议。
简而言之,技术上的区别如下:
IteratorIterator
可以对任何Traversable
进行线性遍历,而RecursiveIteratorIterator
需要更具体的RecursiveIterator
来循环遍历树。IteratorIterator
通过getInnerIerator()
公开其主要Iterator
,而RecursiveIteratorIterator
仅通过该方法提供当前活动的子Iterator
。IteratorIterator
完全不知道父级或子级之类的任何内容,而RecursiveIteratorIterator
也知道如何获取和遍历子级。IteratorIterator
不需要迭代器堆栈,而RecursiveIteratorIterator
有这样的堆栈并知道活动子迭代器。IteratorIterator
由于线性无选择,因此具有其顺序,而RecursiveIteratorIterator
具有进一步遍历的选择,并且需要针对每个节点进行决策(通过mode per RecursiveIteratorIterator
决定)。RecursiveIteratorIterator
比IteratorIterator
拥有更多的方法。RecursiveIterator
是迭代的具体类型(遍历树),它可以使用自己的迭代器,即RecursiveIterator
。这与IteratorIerator
的基本原理相同,但迭代类型不同(线性顺序)。Traversable
,这可以通过Iterator
或IteratorAggregate
实现。然后您就可以将其与foreach
一起使用。例如,一些三叉树遍历递归迭代对象以及相应的容器对象迭代接口。
当一个具有线性顺序的迭代器只遍历顶层文件夹和文件(单个目录清单)时,递归迭代器也遍历子文件夹,并列出所有文件夹和文件(带有其子目录列表的目录清单)。
Non-Recursive Recursive
============= =========
[tree] [tree]
├ dirA ├ dirA
└ fileA │ ├ dirB
│ │ └ fileD
│ ├ fileB
│ └ fileC
└ fileA
IteratorIterator
进行比较,后者不会递归遍历目录树。而RecursiveIteratorIterator
可以像递归列表一样进入树中进行遍历。Traversable
的DirectoryIterator
,允许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
默认只知道名为线性顺序的迭代类型,我们可能希望明确指定迭代类型。乍一看,它可能过于冗长,但出于演示目的(并为了使与稍后的RecursiveIteratorIterator
的差异更加明显),让我们明确指定迭代器类型为IteratorIterator
来列出目录清单中的线性迭代类型:
$files = new IteratorIterator($dir);
echo "[$path]\n";
foreach ($files as $file) {
echo " ├ $file\n";
}
$files
现在是一个IteratorIterator
类型的迭代器,用于遍历可遍历的$dir
。$files = new IteratorIterator($dir);
通常情况下,迭代的行为由foreach
执行:
foreach ($files as $file) {
foreach
内使用的对象。在第一个示例中,它是一个DirectoryIterator
,而在第二个示例中,它是IteratorIterator
。这显示了迭代器具有的灵活性:您可以将它们彼此替换,foreach
内部的代码仍然按预期工作。foreach
与之配合工作,我们需要一种不同类型的迭代器:RecursiveIteratorIterator
。而且,只有实现RecursiveIterator
接口的容器对象才能进行迭代。
接口是一种契约。任何实现它的类都可以与RecursiveIteratorIterator
一起使用。这样一个类的例子是RecursiveDirectoryIterator
,它类似于DirectoryIterator
的递归变体。
在写任何其他有关“接口”的句子之前,让我们先看一个代码示例:
$dir = new RecursiveDirectoryIterator($path);
echo "[$path]\n";
foreach ($dir as $file) {
echo " ├ $file\n";
}
[tree]
├ tree\.
├ tree\..
├ tree\dirA
├ tree\fileA
好的,现在文件名包含路径名,但其余部分看起来也很相似。
正如示例所示,即使目录对象已经实现了RecursiveIterator
接口,这还不足以使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
这个例子可能不太好看,但是它展示了使用递归迭代器可以获得比仅有key和value的线性顺序更多的信息。即使foreach
也只能表达这种线性关系,访问迭代器本身可以获取更多的信息。
类似于元信息,遍历树的方式和输出顺序也有不同的可能性。这是RecursiveIteratorIterator
的Mode模式,可以在构造函数中设置。
下一个例子将告诉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
中并完成操作。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
正常工作。。RecursiveIteratorIterator
如何工作**(例如,它知道如何调用getChildren()
)。 - salatheRecursiveIteratorIterator
的具体实现细节,因为这与其他类型相同,但我确实提供了一些技术信息以说明它的实际工作原理。我认为示例很好地展示了它们之间的差异:迭代类型是它们之间的主要区别。我不知道你是否同意我的说法,但是在语义上,迭代类型有时难以简单地用一种方式表达。 - hakre什么是
IteratorIterator
和RecursiveIteratorIterator
之间的区别?
要理解这两个迭代器之间的差异,首先必须了解一些命名约定以及我们所谓的“递归”迭代器。
PHP有非“递归”迭代器,例如ArrayIterator
和FilesystemIterator
。还有诸如RecursiveArrayIterator
和RecursiveDirectoryIterator
等“递归”迭代器。后者具有使它们能够进入子级的方法,前者则没有。
即使是递归迭代器本身,当对其进行循环遍历时,值也只来自“顶层”,即使对嵌套数组或具有子目录的目录进行循环遍历也是如此。
递归迭代器通过实现hasChildren()
、getChildren()
方法来实现递归行为,但不会利用递归行为。
最好将递归迭代器视为“可递归”迭代器,它们具有被递归迭代的能力,但仅仅迭代这些类的实例并不能做到这一点。要利用递归行为,请继续阅读。
这就是RecursiveIteratorIterator
发挥作用的地方。它知道如何以这样一种方式调用“可递归”迭代器,以便以正常、平坦的循环方式深入到结构中。它把递归行为付诸实践。它基本上是在每个迭代器值上进行步进的工作,查看是否有“子级”需要递归,以及进入和退出那些子级集合。将RecursiveIteratorIterator
的实例插入到foreach中,它会自动深入结构,因此您不必自己编写递归循环。
如果不使用RecursiveIteratorIterator
,则必须编写自己的递归循环以利用递归行为,检查“可递归”迭代器的hasChildren()
并使用getChildren()
。
因此,这是对RecursiveIteratorIterator
的简要概述,它与IteratorIterator
有何不同?嗯,你基本上是在问与树干区分猫咪和树木的区别。只是因为这两者出现在同一部百科全书(或手册,用于迭代器)中,并不意味着你应该混淆两者。
IteratorIterator
的任务是获取任何可遍历对象,并包装它以满足Iterator
接口。其用途是能够在非迭代对象上应用特定于迭代器的行为。
举个实际例子,DatePeriod
类是可遍历的但不是Iterator
。因此,我们可以使用foreach()
循环遍历其值,但不能像迭代器那样执行其他操作,例如筛选。
任务:$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
对象。
IteratorIterator
不就是Traversable
对象的标准线性顺序遍历类型吗?那些可以在foreach
中直接使用而不需要它的对象呢?而且,一个RecursiveIterator
难道不总是一个Traversable
,因此不仅是IteratorIterator
,而且也总是“用于将Iterator
行为应用于非迭代器、可遍历对象”的RecursiveIteratorIterator
吗?(我现在会说,foreach
通过容器对象上实现迭代器类型接口的迭代器对象应用迭代类型,因此这些是迭代器容器对象,总是Traversable
) - hakreIteratorIterator
是一个类,其主要功能是将 Traversable
对象包装在一个 Iterator
中。没有其他作用。你似乎将该术语应用得更加普遍。 - salatheRecursiveIterator
中的“Recursive” 暗示了行为,与之相反,更合适的名称应该描述迭代器的能力,例如RecursibleIterator
。因为名字误导了很长时间。请注意我尽力使翻译通俗易懂,但不改变原意。 - goatRecursiveIteratorIterator
包装。如果是这样的话,那么我认为PHP语言应该摆脱公共的RecursiveIteratorIterator
类,而是应该返回一个可以直接在foreach循环中递归迭代的迭代器,当你想要使用RecursiveIterators而不使用RecursiveIteratorIterator
时,是否存在任何用例呢? - AdamRecursiveIteratorIterator
类,因为它是递归遍历的事实标准实现,可以与RecursiveIterator
接口合同。也许这是一个“你不能既要饼干又想吃掉”的情况。然而,你提出的问题是有依据的。我的建议是:当你已经知道你的类型具有(仅此种类型的)遍历时,将其作为迭代器聚合体(implements IteratorAggregate
)并直接从getIterator()
方法中提供该迭代器。 - hakreRecursiveDirectoryIterator会显示整个路径名,而不仅仅是文件名。其他的看起来都很好。这是因为文件名是由SplFileInfo生成的。应该将它们显示为basename。期望的输出如下:
$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
将递归遍历数组以查找所有值。这意味着它会展开原始数组。
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));
new IteratorIterator(new ArrayIterator($array))
等同于 new ArrayIterator($array)
,也就是说,外部的 IteratorIterator
没有起到任何作用。此外,输出结果的展平与 iterator_to_array
没有任何关系 - 它只是将迭代器转换为数组。展平是由 RecursiveArrayIterator
遍历其内部迭代器的方式决定的属性。 - Quolonel Questions
RecursiveIteratorIterator
如何工作,那么你是否已经理解了IteratorIterator
的工作原理呢?我的意思是它们基本上是相同的,只是被两者使用的接口不同而已。你更感兴趣的是一些示例还是想看底层 C 代码实现的差异呢? - hakreIteratorIterator
将Iterator
和IteratorAggregate
映射到一个Iterator
中,而RecursiveIteratorIterator
用于递归遍历RecursiveIterator
。 - Adam