遍历文件夹的方法比较。

遍历文件夹的方法比较。

本贴对三种遍历文件夹方法比较。
1. 使用File::Find;
2. 递归遍历。(遍历函数为lsr)
3. 使用队列或栈遍历。(遍历函数为lsr_s)

1.use File::Find

[Copy to clipboard] [ - ]
CODE:
#!/usr/bin/perl -W
#
# File: find.pl
# Author:  路小佳
# License: GPL-2

use strict;
use warnings;
use File::Find;

my ($size, $dircnt, $filecnt) = (0, 0, 0);

sub process {
    my $file = $File::Find::name;
    #print $file, "\n";
    if (-d $file) {
        $dircnt++;
    }
    else {
        $filecnt++;
        $size += -s $file;
    }
}

find(\&process, '.');
print "$filecnt files, $dircnt directory. $size bytes.\n";

2. lsr递归遍历

[Copy to clipboard] [ - ]
CODE:
#!/usr/bin/perl -W
#
# File: lsr.pl
# Author: 路小佳
# License: GPL-2

use strict;
use warnings;

sub lsr($) {
    sub lsr;
    my $cwd = shift;

    local *DH;
    if (!opendir(DH, $cwd)) {
        warn "Cannot opendir $cwd: $! $^E";
        return undef;
    }
    foreach (readdir(DH)) {
        if ($_ eq '.' || $_ eq '..') {
            next;
        }
        my $file = $cwd.'/'.$_;
        if (!-l $file && -d _) {
            $file .= '/';
            lsr($file);
        }
        process($file, $cwd);
    }
    closedir(DH);
}

my ($size, $dircnt, $filecnt) = (0, 0, 0);

sub process($$) {
    my $file = shift;
    #print $file, "\n";
    if (substr($file, length($file)-1, 1) eq '/') {
        $dircnt++;
    }
    else {
        $filecnt++;
        $size += -s $file;
    }
}

lsr('.');
print "$filecnt files, $dircnt directory. $size bytes.\n";

3. lsr_s栈遍历

[Copy to clipboard] [ - ]
CODE:
#!/usr/bin/perl -W
#
# File: lsr_s.pl
# Author: 路小佳
# License: GPL-2

use strict;
use warnings;

sub lsr_s($) {
    my $cwd = shift;
    my @dirs = ($cwd.'/');

    my ($dir, $file);
    while ($dir = pop(@dirs)) {
        local *DH;
        if (!opendir(DH, $dir)) {
            warn "Cannot opendir $dir: $! $^E";
            next;
        }
        foreach (readdir(DH)) {
            if ($_ eq '.' || $_ eq '..') {
                next;
            }
            $file = $dir.$_;         
            if (!-l $file && -d _) {
                $file .= '/';
                push(@dirs, $file);
            }
            process($file, $dir);
        }
        closedir(DH);
    }
}

my ($size, $dircnt, $filecnt) = (0, 0, 0);

sub process($$) {
    my $file = shift;
    print $file, "\n";
    if (substr($file, length($file)-1, 1) eq '/') {
        $dircnt++;
    }
    else {
        $filecnt++;
        $size += -s $file;
    }
}

lsr_s('.');
print "$filecnt files, $dircnt directory. $size bytes.\n";

对我的硬盘/dev/hda6的测试结果。

1: File::Find

[Copy to clipboard] [ - ]
CODE:
26881 files, 1603 directory. 9052479946 bytes.

real    0m9.140s
user    0m3.124s
sys     0m5.811s

2: lsr

[Copy to clipboard] [ - ]
CODE:
26881 files, 1603 directory. 9052479946 bytes.

real    0m8.266s
user    0m2.686s
sys     0m5.405s

3: lsr_s

[Copy to clipboard] [ - ]
CODE:
26881 files, 1603 directory. 9052479946 bytes.

real    0m6.532s
user    0m2.124s
sys     0m3.952s

测试时考虑到cache所以要多测几次取平均, 也不要同时打印文件名, 因为控制台是慢设备, 会形成瓶颈。
lsr_s之所以用栈而不是队列来遍历,是因为Perl的push shift pop操作是基于数组的, push pop这样成对操作可能有优化。内存和cpu占用大小顺序也是1>2>3.

[Copy to clipboard] [ - ]
CODE:
                           CPU load          memory
use File::Find             97%               4540K
lsr                        95%               3760K
lsr_s                      95%               3590K

结论: 强烈推荐使用lsr_s来遍历文件夹。

=============再罗嗦几句======================
从执行效率上来看,find.pl比lsr.pl的差距主要在user上, 原因是File::Find模块选项较多, 条件判断费时较多,而lsr_s.pl比lsr.pl在作系统调用用时较少, 是因为递归时程序还在保存原有的文件句柄和函数恢复现场的信息, 所以sys费时较多。 所以lsr_s在sys与user上同时胜出是不无道理的。

仙子好快,谢谢鼓励。
这两个遍历脚本我觉得已经努力按照C语言的风格优化了, 但是对我对Perl了解还不够, 如果代码还有明显的概念错误或性能还有提升的余地, 各位大虾不吝赐教。
(-d $file && !-l $file)

类似这样的操作,第2个$file可以用_替换吧?表示重用前一个文件句柄.
啊呀, 还有这样的用法, 谢谢指点。
抱怨一下Perl语法, 像(-d $file && !-l _)这样的技巧性的东西太多导致了Perl的学习成本太高, 而Perl6更有发扬光大的趋势。这让我很不安。当初抱着《Perl高级编程》啃了三个月, 头大如斗, 还没看完就被催着还掉了。我看Perl当前应该做的优化性能和整合模块。Parrot这样的东西应该是Perl的希望, 而CPAN上模块虽多,质量却参差不齐(File::Find已经算很好用的模块了)。只是Perl6一直跳票, 还附带了许多新语法, 而整合模块这是本身又与Perl哲学不合(所谓哲学怕是一个借口, 大约是没人愿意也没人响应来做这事)。
最后用一句名言来反驳我自己:"当程序员还在用COBOL编程时,人类已经登上了月球。", 但是Perl虽好, 奈何学习成本太高。


QUOTE:
当程序员还在用COBOL编程时,人类已经登上了月球

那个时候有没有C程序?他们不会用汇编编的登月系统吧?
这个名言好像是Stroustrup说的, 说服人们不要老做C++ vs Java的无意义的争吵。
老师也告诉我们, 任何语言只是工具, 就像建高楼的推土机,脚手架等等。就像从来没有人声称自己是“推土机高手”, 宣称自己是"XX语言"的高手也很可笑,最重要的是做设计高手。
但是Perl这个工具虽然用起来挺趁手, 但是按钮和注意事项太多,让我等初涉Perl的人有点目不暇接。比如像(!-l $file && -d _)这种, 刚才测试有了了1%~5%的提升, 高兴之余不免有点郁闷。


QUOTE:
原帖由 路小佳 于 2007-3-10 23:58 发表
这个名言好像是Stroustrup说的, 说服人们不要老做C++ vs Java的无意义的争吵。
老师也告诉我们, 任何语言只是工具, 就像建高楼的推土机,脚手架等等。就像从来没有人声称自己是“推土机高手”, 宣称自己是&q ...

Perl best practice里有讲这些的...
另外_是常用用法了,可能你用的少就不习惯吧.


QUOTE:
那个时候有没有C程序?他们不会用汇编编的登月系统吧?

地上的大多可能是C写的,登月的基本就是汇编写的了。前一段时间看了一段新闻,说美国宇航局专门派人到某电子废物处理中心买了一货柜旧电子产品,目的就是拆其中的8086来备用,因为现有的航天飞机和各种型号的火箭、导弹的核心都是她,这玩意还得继续维护直到退役。

从另一个角度想,中国的军工还是有后发优势的,只是很多东西没做过没这个胆而已。
哈哈," 可能你用的少就不习惯吧", 仙子不用给我留面子,像flw直接指出来也挺好的。 我知道Perl上我还是门外汉。现在我的水平也就是停留在用C语言来写Perl的地步。不说了, 有版聊嫌疑, 晚安。