RecursiveIteratorIterator

RecursiveIteratorIterator

RecursiveIteratorIterator如何工作?

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

最佳答案

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

  • 总结一下:RecursiveIterator是一种具体的迭代类型(在树上循环),可在其自己的迭代器上工作,即RecursiveIterator。这与IteratorIerator的基本原理相同,但是迭代类型不同(线性顺序)。

    理想情况下,您也可以创建自己的集合。唯一需要做的是,您的迭代器实现Traversable,可以通过IteratorIteratorAggregate来实现。然后,您可以将其与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
    

    如您所见,这尚未使用IteratorIteratorRecursiveIteratorIterator。相反,它仅使用在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

    09-10 03:42