我正在创建一个子程序:
(1)解析CSV文件;
(2)并检查该文件中的所有行是否具有预期的列数。如果列数无效,则会出现问题。
如果行数从数千到数百万不等,您认为最有效的方式是什么?
现在,我正在尝试这些实现。
(1)基本文件解析器
open my $in_fh, '<', $file or
croak "Cannot open '$file': $OS_ERROR";
my $row_no = 0;
while ( my $row = <$in_fh> ) {
my @values = split (q{,}, $row);
++$row_no;
if ( scalar @values < $min_cols_no ) {
croak "Invalid file format. File '$file' does not have '$min_cols_no' columns in line '$row_no'.";
}
}
close $in_fh
or croak "Cannot close '$file': $OS_ERROR";
(2)使用Text :: CSV_XS(bind_columns和csv-&gt; getline)
my $csv = Text::CSV_XS->new () or
croak "Cannot use CSV: " . Text::CSV_XS->error_diag();
open my $in_fh, '<', $file or
croak "Cannot open '$file': $OS_ERROR";
my $row_no = 1;
my @cols = @{$csv->getline($in_fh)};
my $row = {};
$csv->bind_columns (\@{$row}{@cols});
while ($csv->getline ($in_fh)) {
++$row_no;
if ( scalar keys %$row < $min_cols_no ) {
croak "Invalid file format. File '$file' does not have '$min_cols_no' columns in line '$row_no'.";
}
}
$csv->eof or $csv->error_diag();
close $in_fh or
croak "Cannot close '$file': $OS_ERROR";
(3)使用Text :: CSV_XS(csv-&gt; parse)
my $csv = Text::CSV_XS->new() or
croak "Cannot use CSV: " . Text::CSV_XS->error_diag();
open my $in_fh, '<', $file or
croak "Cannot open '$file': $OS_ERROR";
my $row_no = 0;
while ( <$in_fh> ) {
$csv->parse($_);
++$row_no;
if ( scalar $csv->fields < $min_cols_no ) {
croak "Invalid file format. File '$file' does not have '$min_cols_no' columns in line '$row_no'.";
}
}
$csv->eof or $csv->error_diag();
close $in_fh or
croak "Cannot close '$file': $OS_ERROR";
(4)使用Parse :: CSV
use Parse::CSV;
my $simple = Parse::CSV->new(
file => $file
);
my $row_no = 0;
while ( my $array_ref = $simple->fetch ) {
++$row_no;
if ( scalar @$array_ref < $min_cols_no ) {
croak "Invalid file format. File '$file' does not have '$min_cols_no' columns in line '$row_no'.";
}
}
我使用Benchmark模块对它们进行基准测试。
use Benchmark qw(timeit timestr timediff :hireswallclock);
这些是我得到的数字(以秒为单位):
1,000行文件:
实施1:0.0016
实施2:0.0025
实施3:0.0050
实施4:0.0097
10,000行文件:
实施1:0.0204
实施2:0.0244
实施3:0.0523
实施4:0.1050
1,500,000行文件:
实施1:1.8697
实施2:3.1913
实施3:7.8475
实施4:15.6274
鉴于这些数字,我会得出结论,简单的解析器是最快的,但从我从不同来源读到的,Text :: CSV_XS应该是最快的。
有人会对此启发吗?我如何使用这些模块有什么问题吗?非常感谢你的帮助!
答案 0 :(得分:16)
有CSV文件
header1,header2,header3
value1,value2,value3
然后有CSV文件。
header1,"This, as they say, is header2","And header3
even contains a newline!"
value1,"value2, 2nd in a series of 3 values",value3
Text::CSV
及其同类经过精心研制和测试,以应对第二种类型。如果您确信您的输入确实并且始终符合简单的CSV规范,那么您很可能可以构建一个优于Text::CSV
的解析器。
答案 1 :(得分:9)
请注意,您的Text::CSV_XS
版本比简单的解析器版本更多。它将行拆分,将其放入内存,并使hashref指向字段。
它也可能有其他逻辑,比如允许转义分隔符(我不知道,因为我没有使用它)。最重要的是,在使用模块时总是会有少量开销:函数调用,来回传递参数,以及可能在您的情况下并不真正适用的通用代码(例如错误检查您要做的事情)关心)。
通常,使用模块的好处大大超过了成本。您可以获得更多功能,更可靠的代码等。但是,对于一个小而简单的任务,这可能不是真的。如果您只需要验证列数,则使用模块可能会过度。您可以通过计算列数来更快地实现自己的实现,而不必费心去分割:
/(?:,[^,]*){$min_cols_no-1}/ or croak "Did not find minimum number of columns";
如果您要在此验证步骤之外进行实际处理,使用该模块可能会有所帮助。
答案 2 :(得分:1)
所有CSV解析模块都做同样的事情:打开文件并以某种方式解析CSV,就像在基本子中一样。它们只是带来了更多的开销,因为在内部,它们比你需要的要多得多(检查适当的CSV格式,传递对象结构等)。这使得它们在不同程度上比基本方法慢。
您自己对这些方法进行了基准测试;结果不明显吗?如果我不需要CSV模块的扩展功能,我会以自己的基本方式解析CSV文件。
(我不知道你是否可以通过提高模块的使用来加速它们)
答案 3 :(得分:0)
为了好玩,我为此测试了regexp ......它有效! ;)如果你有足够的ram,你可以一次读取整个文件,然后使用正则表达式:
my $blob = 'a;s;d
q;w;e
r;t;y
u;i;o
p;z;x
c;;b
n;m;f
g;h;j
k;l;';
say $blob =~ /^ ([^;]*;){2}[^;]* (\n (([^;]*;){2}[^;]*)+ \n ([^;]*;){2}[^;]*)? $/x ? 'ok' : 'bu';
但这不包括分隔符转义,引用等 - 只测试指定数量的分隔符:)