【问题标题】:Apply Trailing Slash to Folders with FilesystemIterator使用 FilesystemIterator 对文件夹应用尾部斜杠
【发布时间】:2012-10-20 12:00:04
【问题描述】:

有如下sn-p递归映射当前目录的内容:

$files = new RecursiveIteratorIterator
(
    new RecursiveDirectoryIterator('./',
        FilesystemIterator::SKIP_DOTS | FilesystemIterator::UNIX_PATHS),
    RecursiveIteratorIterator::SELF_FIRST
);

$files = array_values(array_map('strval', iterator_to_array($files)));

返回如下内容:

Array
(
    [0] => ./1.png
    [1] => ./a.php
    [2] => ./adminer
    [3] => ./adminer/adminer.css
    [4] => ./adminer/adminer.php
)

有什么方法可以让RecursiveDirectoryIterator / FilesystemIterator 模拟glob() 函数中存在的GLOB_MARK 行为?来自手册:

GLOB_MARK - 在每个返回的目录中添加一个斜杠。

我知道我可以通过这样做来效仿:

foreach ($files as $key => $value)
{
    $files[$key] .= (is_dir($value) ? '/' : '');
}

但这需要(不必要地?)多次访问磁盘。我正在寻找一种方法来快速确定路径是目录还是常规文件,而结尾的斜杠似乎是理想的解决方案。

我计划用这个遍历成千上万(如果不是数百)数千个文件,因此性能至关重要。

额外问题:有没有办法只获取目录(递归)?

【问题讨论】:

  • 您介意扩展RecursiveDirectoryIterator的解决方案吗??
  • @Baba: 一点也不,但请注意,在里面打电话给isDir() 是作弊! :P
  • 我知道....我也想作弊... :)

标签: php filesystems iterator spl


【解决方案1】:

您可以创建自己的GlobMarkIterator 优势:

  • 将结束斜杠返回到目录,就像GLOB_MARK
  • 无需使用array_mapstrval 将其转换为字符串
  • 没有额外的foreach 循环与is_dir
  • 还是和原来一样快
  • 是的,我知道我作弊了

例子

$ri = new RecursiveIteratorIterator(new GlobMarkIterator('./', FilesystemIterator::SKIP_DOTS | FilesystemIterator::UNIX_PATHS), RecursiveIteratorIterator::SELF_FIRST);
$files = array_values(iterator_to_array($ri));

echo "<pre>";
print_r($files);

输出

Array
(
    [0] => ./test/backups/ <----------- Note ending slash 
    [1] => ./test/CSV/
    [2] => ./test/CSV/abc.csv
    [3] => ./test/final/
    [4] => ./test/thumb/
    [5] => ./test/thumb/a.png
    [6] => ./test/thumb/s.svg
    [7] => ./test/thumb/sample.svg
)



额外问题:有没有办法只获取目录(递归)?

这应该是另一个问题,但都是一样的...... 我希望你不要对此感到满意并为此提供赏金

解决方案:

$ri = new RecursiveIteratorIterator( new GlobMarkDirectory('./test'), RecursiveIteratorIterator::SELF_FIRST);
$dir = array_values(iterator_to_array($ri));

echo "<pre>";
print_r($dir);

输出

Array
(
    [0] => ./test/backups/
    [1] => ./test/CSV/
    [2] => ./test/final/
    [3] => ./test/thumb/
)

使用的类

GlobMarkIterator

class GlobMarkIterator extends RecursiveDirectoryIterator {
    function current() {
        return $this->isDir() ? $this->getPathname() . "/" : $this->getPathname();
    }
}

GlobMarkDirectory

class GlobMarkDirectory  extends RecursiveFilterIterator {
    public function __construct($path) {
        parent::__construct(new GlobMarkIterator($path, FilesystemIterator::SKIP_DOTS | FilesystemIterator::UNIX_PATHS));
    }
    public function accept() {
        return $this->getInnerIterator()->isDir();
    }
    public function getChildren() {
        return new GlobMarkDirectory($this->getInnerIterator()->getPathname());
    }
}



编辑 .. 如果您不关心 empty dir 并且由于速度和开销而不想使用 isDir,这里是另一种解决方案

解决方案

$ri = new RecursiveIteratorIterator(new GlobMarkFastDirectory  (__DIR__), RecursiveIteratorIterator::SELF_FIRST);
$dir = array_values(array_unique(iterator_to_array($ri)));

GlobMarkFastDirectory

class GlobMarkFastDirectory  extends RecursiveDirectoryIterator {
    function current() {
        return dirname($this->getPathname())  ."/";
    }
}

【讨论】:

  • 谢谢,它真的和准系统一样快吗?如果我必须在 145.684 文件上调用 isDir() 怎么办? RecursiveIteratorIterator::SELF_FIRST 首先放置目录,不能以某种方式利用,例如使用 next() 吗?此外,getChildren() / hasChildren() 方法是否会比 isDir() 提供任何好处?我的印象是isDir() 发出另一个磁盘调用,但我可能错了。
  • 也可以被利用,但问题是由于仓位管理,next 可能非常棘手.....isDirSplFileInfo::isDir 不同 .. 类型已经在它之前确定被称为.. RecursiveDirectoryIterator 中的每个文件都返回SplFileInfo .. 它已经知道它是文件还是目录... 它不再搜索或定位文件
  • 在这种情况下,这是完美的。谢谢!
  • 刚刚为您的额外问题添加了解决方案:)
  • 我注意到了!关于“类型在被调用之前已经确定”,我的一个朋友只是向我指出了这些 LOC:github.com/php/php-src/blob/master/ext/spl/…github.com/php/php-src/blob/master/ext/standard/… 所以我想这并不完全准确,但显然 glob 做了同样的事情:@987654323 @.
【解决方案2】:

这是我迄今为止的最大努力:

$files = new RecursiveIteratorIterator
(
    new RecursiveDirectoryIterator
    (
        str_replace('\\', '/', realpath('./')),
        FilesystemIterator::SKIP_DOTS | FilesystemIterator::UNIX_PATHS
    ),
    RecursiveIteratorIterator::LEAVES_ONLY
);

$files = array_keys(iterator_to_array($files));
$folders = array();

/*
foreach ($files as $key => $value) // not needed anymore
{
    $files[$key] .= (is_dir($value) === true) ? '/' : '';
}
*/

$files = array_flip($files);

foreach ($files as $key => $value)
{
    $folder = dirname($key) . '/'; // doesn't issue a stat call

    if (array_key_exists($folder, $folders) !== true)
    {
        $folders[$folder] = 0;
    }

    $folders[$folder] += $files[$key] = sprintf('%u', filesize($key));
}

我可以将 $folders 数组与 $files 数组合并以准确回答问题,但区分另一个正是我的主要目标,所以这样做没有意义。

【讨论】:

  • @Baba:这只是一个概念证明。它在不到 10 秒的时间内运行 47k 个文件,但我最终会打磨原始边缘。 =)
  • +1 不使用isDir ... 让我看看如何改进它:)
  • 我刚刚用你的脚本观察了一个数组..它跳过了一些目录
  • 我的意思是skip empty folders
  • @Baba:确实,但我打算用它来构建一个比平均水平更好的脚本来识别重复的文件夹、文件和代码——所以在这种情况下这不是一个大问题。
猜你喜欢
  • 2016-12-17
  • 1970-01-01
  • 1970-01-01
  • 2022-01-23
  • 2021-12-27
  • 2011-07-24
  • 2013-02-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多