RecursiveIteratorIterator 如何在 PHP 中工作?

2022-08-30 07:47:47

如何运作?RecursiveIteratorIterator

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


答案 1

RecursiveIteratorIterator是一个实现树遍历的具体迭代。它使程序员能够遍历实现接口的容器对象,请参阅维基百科中的迭代器,了解迭代器的一般原则,类型,语义和模式。RecursiveIterator

迭代器不同,IteratorIterator是一个具体的实现对象遍历线性顺序(并且默认情况下在其构造函数中接受任何类型的可遍历),允许循环遍历对象的有序树中的所有节点,其构造函数采用.IteratorRecursiveIteratorIteratorRecursiveIterator

简而言之:允许您循环访问树,允许您循环访问列表。我很快就会在下面的一些代码示例中展示这一点。RecursiveIteratorIteratorIteratorIterator

从技术上讲,这是通过遍历节点的所有子节点(如果有的话)来打破线性来工作的。这是可能的,因为根据定义,节点的所有子节点都是 .然后,toplevel在内部按深度堆叠不同的s,并保留指向当前活动子的指针以进行遍历。RecursiveIteratorIteratorRecursiveIteratorIterator

这允许访问树的所有节点。

一个接口指定迭代的类型,基迭代器类是这些语义的实现。与下面的示例相比,对于线性循环,通常不要考虑太多实现细节,除非您需要定义一个新的(例如,当某些具体类型本身不实现时)。IteratorIteratorforeachIteratorTraversable

对于递归遍历 - 除非您不使用已经具有递归遍历迭代的预定义 - 否则您通常需要实例化现有迭代,甚至编写您自己的递归遍历迭代才能使用 这种类型的遍历迭代。TraversalRecursiveIteratorIteratorTraversableforeach

提示:你可能没有实现一个,也没有实现另一个你自己的,所以这可能是值得做的事情,因为你对它们的差异的实际体验。您可以在答案的末尾找到一个DIY建议。

技术差异简述:

  • 虽然采取任何线性遍历,但需要一个更具体的循环到树上。IteratorIteratorTraversableRecursiveIteratorIteratorRecursiveIterator
  • 其中公开其主 via ,仅通过该方法提供当前活动的子。IteratorIteratorIteratorgetInnerIerator()RecursiveIteratorIteratorIterator
  • 虽然完全不知道父母或孩子之类的任何事情,但也知道如何获得和穿越孩子。IteratorIteratorRecursiveIteratorIterator
  • IteratorIterator不需要迭代器堆栈,具有这样的堆栈并且知道活动的子迭代器。RecursiveIteratorIterator
  • 其中由于线性而无选择,其顺序为进一步遍历而有选择,并且需要根据每个节点决定(通过每个递归迭代器的模式决定)。IteratorIteratorRecursiveIteratorIterator
  • RecursiveIteratorIterator具有比 的方法多。IteratorIterator

总而言之:是一种具体类型的迭代(在树上循环),它在其自己的迭代器上工作,即。这与 相同的基本原理与 相同,但迭代的类型不同(线性顺序)。RecursiveIteratorRecursiveIteratorIteratorIerator

理想情况下,您也可以创建自己的套装。唯一必要的是迭代器实现,这可以通过 或 实现。然后,您可以将其与 一起使用。例如,某种三元树遍历递归迭代对象以及容器对象的相应迭代接口。TraversableIteratorIteratorAggregateforeach


让我们回顾一些现实生活中不那么抽象的例子。在接口,具体迭代器,容器对象和迭代语义之间,这可能不是一个坏主意。

以目录列表为例。假设磁盘上有以下文件和目录树:

Directory Tree

当具有线性顺序的迭代器仅遍历 toplevel 文件夹和文件(单个目录列表)时,递归迭代器也会遍历子文件夹并列出所有文件夹和文件(目录列表及其子目录列表):

Non-Recursive        Recursive
=============        =========

   [tree]            [tree]
    ├ dirA            ├ dirA
    └ fileA           │ ├ dirB
                      │ │ └ fileD
                      │ ├ fileB
                      │ └ fileC
                      └ fileA

您可以轻松地将其与它进行比较,它不执行遍历目录树的递归。并且可以像递归列表所示的那样遍历到树中。IteratorIteratorRecursiveIteratorIterator

首先,一个非常基本的例子,它带有一个实现Traversable的Dretariterator,它允许foreach迭代它:

$path = 'tree';
$dir  = new DirectoryIterator($path);

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

然后,上述目录结构的示例性输出为:

[tree]
 ├ .
 ├ ..
 ├ dirA
 ├ fileA

如您所见,这尚未使用 或 。相反,它只是使用在界面上运行的操作。IteratorIteratorRecursiveIteratorIteratorforeachTraversable

由于默认情况下只知道名为线性顺序的迭代类型,因此我们可能希望显式指定迭代类型。乍一看,它可能看起来太冗长,但出于演示目的(并使区别在以后更明显),让我们指定线性迭代类型,显式指定目录列表的迭代类型:foreachRecursiveIteratorIteratorIteratorIterator

$files = new IteratorIterator($dir);

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

此示例与第一个示例几乎相同,不同之处在于,它现在是以下的一种迭代类型:$filesIteratorIteratorTraversable$dir

$files = new IteratorIterator($dir);

像往常一样,迭代行为由执行:foreach

foreach ($files as $file) {

输出完全相同。那么有什么不同呢?不同之处在于 .在第一个示例中,它是第二个示例中的 .这显示了迭代器的灵活性:您可以相互替换它们,内部代码只是继续按预期工作。foreachDirectoryIteratorIteratorIteratorforeach

让我们开始获取整个列表,包括子目录。

由于我们现在已经指定了迭代类型,因此让我们考虑将其更改为另一种类型的迭代。

我们知道我们现在需要遍历整棵树,而不仅仅是第一层。为了与一个简单的迭代器一起工作,我们需要一个不同类型的迭代器:递归迭代器Iterator。并且只能迭代具有递归器接口的容器对象。foreach

接口是一个合约。任何实现它的类都可以与 一起使用。此类的一个例子是 RecursiveDirectoryIterator,它类似于 的递归变体。RecursiveIteratorIteratorDirectoryIterator

让我们在用 I 字编写任何其他句子之前,先看一下第一个代码示例:

$dir  = new RecursiveDirectoryIterator($path);

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

第三个示例与第一个示例几乎相同,但它创建了一些不同的输出:

[tree]
 ├ tree\.
 ├ tree\..
 ├ tree\dirA
 ├ tree\fileA

好吧,没有那么不同,文件名现在在前面包含路径名,但其余的看起来也相似。

如示例所示,即使目录对象已经集成了接口,这还不足以遍历整个目录树。这就是行动的地方。示例 4 显示了如何:RecursiveIteratorforeachRecursiveIteratorIterator

$files = new RecursiveIteratorIterator($dir);

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

使用 而不是仅使用上一个对象将使以递归方式遍历所有文件和目录。然后,这将列出所有文件,因为现在已经指定了对象小版本的类型:RecursiveIteratorIterator$dirforeach

[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

与元信息类似,也有不同的方法可以遍历树,从而对输出进行排序。这是递归迭代器的模式可以使用构造函数进行设置。

下一个示例将告诉 删除点条目 ( 和 ),因为我们不需要它们。但是,递归模式也将更改为在子元素(子目录中的文件和子子目录)之前首先()取父元素(子目录):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

因此,递归模式控制返回树中的 brach 或叶子的内容和时间,例如:

  • 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

当您将其与标准遍历进行比较时,所有这些内容都不可用。因此,当您需要将头包裹在它周围时,递归迭代会稍微复杂一些,但是它易于使用,因为它的行为就像迭代器一样,您将其放入a并完成。foreach

我认为这些已经足够一个答案了。您可以在以下要点中找到完整的源代码以及显示美观的ascii树的示例:https://gist.github.com/3599532

自己动手:使递归TreeIterator逐行工作。

示例 5 演示了有关迭代器状态的元信息可用。但是,这在迭代中被有目的地演示了。在现实生活中,这自然属于.foreachRecursiveIterator

一个更好的例子是递归TreeIterator,它负责缩进,前缀等。请参阅以下代码片段:

$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

与 结合使用时,它显示整个路径名,而不仅仅是文件名。其余的看起来不错。这是因为文件名是由 生成的。这些应显示为基名。所需的输出如下:RecursiveDirectoryIteratorSplFileInfo

/// Solved ///

[tree]
 ├ dirA
 │ ├ dirB
 │ │ └ fileD
 │ ├ fileB
 │ └ fileC
 └ fileA

创建可与 一起使用的修饰器类,而不是 .它应该提供当前的基本名,而不是路径名。然后,最终的代码片段可能如下所示:RecursiveTreeIteratorRecursiveDirectoryIteratorSplFileInfo

$lines = new RecursiveTreeIterator(
    new DiyRecursiveDecorator($dir)
);
$unicodeTreePrefix($lines);
echo "[$path]\n", implode("\n", iterator_to_array($lines));

这些片段包括附录:自己动手:使递归TreeIterator逐行工作中的要点。$unicodeTreePrefix


答案 2

和 有什么区别?IteratorIteratorRecursiveIteratorIterator

要理解这两个迭代器之间的区别,必须首先了解一下所使用的命名约定以及“递归”迭代器的含义。

递归和非递归迭代器

PHP 具有非“递归”迭代器,例如 和 。还有“递归”迭代器,如 和 。后者有使它们能够深入的方法,而前者则没有。ArrayIteratorFilesystemIteratorRecursiveArrayIteratorRecursiveDirectoryIterator

当这些迭代器的实例自行循环时,即使是递归的实例,即使循环访问具有子目录的嵌套数组或目录,这些值也仅来自“顶级”级别。

递归迭代器实现递归行为(via ,),但不利用它。hasChildren()getChildren()

最好将递归迭代器视为“可递归”迭代器,它们具有递归迭代的能力,但简单地迭代这些类之一的实例不会这样做。要利用递归行为,请继续阅读。

递归迭代器Iterator

这就是发挥作用的地方。它具有如何调用“可递归”迭代器的知识,以便以正常,平坦的循环向下钻取结构。它将递归行为付诸行动。它本质上是执行遍历迭代器中的每个值的工作,查看是否有“子项”可以递归到,并进入和退出这些子项集合。你把一个实例插入一个前方,它就会潜入结构中,这样你就不必这样做了。RecursiveIteratorIteratorRecursiveIteratorIterator

如果未使用,则必须编写自己的递归循环来利用递归行为,检查“可递归”迭代器并使用 .RecursiveIteratorIteratorhasChildren()getChildren()

所以这是一个简要的概述,它与什么不同?好吧,你基本上是在问同样的问题,就像小猫和树有什么区别一样?仅仅因为两者都出现在同一个百科全书中(或手册,对于迭代器)并不意味着你应该在两者之间混淆。RecursiveIteratorIteratorIteratorIterator

迭代器迭代器

的工作是获取任何对象,并将其包装使其满足接口。这样做的一个用途是能够对非迭代器对象应用特定于迭代器的行为。IteratorIteratorTraversableIterator

举一个实际的例子,该类不是 .因此,我们可以循环访问其值,但不能执行通常使用迭代器执行的其他操作,例如筛选。DatePeriodTraversableIteratorforeach()

任务:循环播放未来四周的星期一、星期三和星期五。

是的,通过在 DatePeriod预先使用 if() 并在循环中使用 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) { … }

上面的代码段将不起作用,因为 需要实现接口的类的实例,而该实例不起作用。但是,由于它是,我们可以通过使用轻松满足该要求。CallbackFilterIteratorIteratorDatePeriodTraversableIteratorIterator

$period = new IteratorIterator(new DatePeriod(…));

如您所见,这与迭代器类或递归没有任何关系,这就是 和 之间的区别。IteratorIteratorRecursiveIteratorIterator

总结

RecursiveIteraratorIterator用于迭代(“可递归”迭代器),利用可用的递归行为。RecursiveIterator

IteratorIterator用于将行为应用于非迭代器对象。IteratorTraversable


推荐