RecursiveIteratorIterator如何在PHP中工作?

RecursiveIteratorIterator如何工作?

PHP手册没有太多的文档或解释。 IteratorIteratorRecursiveIteratorIterator什么区别?

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通过模式决定)。
  • RecursiveIteratorIteratorIteratorIterator有更多的方法。

总结一下: 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,明确指定目录列表的IteratorIteratortypes:

 $files = new IteratorIterator($dir); echo "[$path]\n"; foreach ($files as $file) { echo " ├ $file\n"; } 

这个例子和第一个例子几乎是一样的,区别在于$files现在是Traversable $dirIteratorIteratortypes:

 $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 工作。

IteratorIteratorRecursiveIteratorIterator什么区别?

为了理解这两个迭代器之间的区别,首先必须了解一些关于使用的命名约定和我们所说的“recursion”迭代器的含义。

recursion和非recursion迭代器

PHP有非recursion迭代器,比如ArrayIteratorFilesystemIterator 。 还有“recursion”迭代器,如RecursiveArrayIteratorRecursiveDirectoryIterator 。 后者有办法让他们钻进去,前者不行。

当这些迭代器的实例独立循环时,即使循环遍历嵌套数组或子目录的目录,值也只能来自“顶”级。

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没有任何作用,其中存在着IteratorIteratorRecursiveIteratorIterator的区别。

概要

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));