如何在Perl中有效地解析CSV文件?

Mik*_*ike 26 csv perl parsing text split

我正在开发一个涉及在Perl中解析大型csv格式文件的项目,并希望提高效率.

我的方法是split()首先按行显示文件,然后再用split()逗号分隔每行以获取字段.但这是次优的,因为至少需要两次传递数据.(一次用线分开,然后再用每一行分开).这是一个非常大的文件,因此切割加工一半将是整个应用程序的重大改进.

我的问题是,使用内置工具解析大型CSV文件的最有效时间是什么?

注意:每一行都有不同数量的标记,因此我们不能只忽略行并仅用逗号分割.此外,我们可以假设字段将只包含字母数字ascii数据(没有特殊字符或其他技巧).此外,我不想进行并行处理,尽管它可能有效.

编辑

它只能涉及Perl 5.8附带的内置工具.出于官僚主义的原因,我不能使用任何第三方模块(即使托管在cpan上)

另一个编辑

假设我们的解决方案只允许在文件数据完全加载到内存后处理它们.

还有另一个编辑

我刚刚抓住这个问题是多么愚蠢.抱歉浪费你的时间.投票结束.

Mic*_*man 46

正确的方法 - 按一个数量级 - 使用Text :: CSV_XS.它将比您自己可能做的任何事情更快,更强大.如果您决定仅使用核心功能,则根据速度与稳健性的不同,您有几个选项.

关于pure-Perl的最快速度是逐行读取文件,然后天真地分割数据:

my $file = 'somefile.csv';
my @data;
open(my $fh, '<', $file) or die "Can't read file '$file' [$!]\n";
while (my $line = <$fh>) {
    chomp $line;
    my @fields = split(/,/, $line);
    push @data, \@fields;
}
Run Code Online (Sandbox Code Playgroud)

如果任何字段包含嵌入的逗号,则会失败.更健壮(但更慢)的方法是使用Text :: ParseWords.为此,请替换为split:

    my @fields = Text::ParseWords::parse_line(',', 0, $line);
Run Code Online (Sandbox Code Playgroud)

  • @MikeKulls:我不认为这本身就是性能问题.这是进行实际解析而不是盲目地假设每个逗号都是字段分隔符的结果.也就是说,它并没有"稍微慢一些".在一个简单的基准测试中,一个简单的`split`比`parse_line`快10-20倍. (2认同)

jkr*_*mer 19

这是一个也尊重报价的版本(例如foo,bar,"baz,quux",123 -> "foo", "bar", "baz,quux", "123").

sub csvsplit {
        my $line = shift;
        my $sep = (shift or ',');

        return () unless $line;

        my @cells;
        $line =~ s/\r?\n$//;

        my $re = qr/(?:^|$sep)(?:"([^"]*)"|([^$sep]*))/;

        while($line =~ /$re/g) {
                my $value = defined $1 ? $1 : $2;
                push @cells, (defined $value ? $value : '');
        }

        return @cells;
}
Run Code Online (Sandbox Code Playgroud)

像这样使用它:

while(my $line = <FILE>) {
    my @cells = csvsplit($line); # or csvsplit($line, $my_custom_seperator)
}
Run Code Online (Sandbox Code Playgroud)

  • 不要滚动自己的CSV解析例程。做错事情很容易,做错事情也很难,这可能会伤到你。请使用其他张贴者提到的Text :: CSV。 (2认同)
  • 我不会说永远不会自己动手.如果您以某种方式编写一个比现有解决方案更好的内容怎么办? (2认同)

Rob*_*t P 9

正如其他人提到的,正确的方法是使用Text :: CSV,Text::CSV_XS后端(用于FASTEST读取)或Text::CSV_PP后端(如果无法编译XS模块).

如果你被允许在本地获得额外的代码(例如,你自己的个人模块),你可以Text::CSV_PP把它放在本地的某个地方,然后通过use lib变通方法访问它:

use lib '/path/to/my/perllib';
use Text::CSV_PP;
Run Code Online (Sandbox Code Playgroud)

另外,如果没有其他选择将整个文件读入内存并且(我假设)存储在标量中,您仍然可以通过打开标量句柄来读取文件句柄:

my $data = stupid_required_interface_that_reads_the_entire_giant_file();

open my $text_handle, '<', \$data
   or die "Failed to open the handle: $!";
Run Code Online (Sandbox Code Playgroud)

然后通过Text :: CSV接口读取:

my $csv = Text::CSV->new ( { binary => 1 } )
             or die "Cannot use CSV: ".Text::CSV->error_diag ();
while (my $row = $csv->getline($text_handle)) {
    ...
}
Run Code Online (Sandbox Code Playgroud)

或逗号上的次优分割:

while (my $line = <$text_handle>) {
    my @csv = split /,/, $line;
    ... # regular work as before.
}
Run Code Online (Sandbox Code Playgroud)

使用此方法,数据一次只能从标量中复制一点.

  • 第二种最正确的方法是创建`Mike :: Text :: CSV`模块,将源文件从`Text :: CSV`复制到其中,并添加一个关于它是如何被"启发"的免责声明开源Text :: CSV模块. (8认同)