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