perl 在非常大的数据文件中替换字符的最快方法

j1dl9f46  于 2022-11-15  发布在  Perl
关注(0)|答案(2)|浏览(170)

我有一个很大的文件(19M行),并且需要清理数据。我使用的是Windows 11机器。数据正在加载到SQL Server 19中。我目前正在使用Perl脚本删除双引号之间的逗号。我将在下面发布我的脚本。这需要很长时间才能运行。我觉得powershell会更快,但是我似乎不能让它运行我需要的REGEX。

#! /usr/bin/perl -w
use strict;
use warnings;
 

my $SRC = $ARGV[0];
my $DES = $ARGV[1];
open(SRC,'<',$SRC) or die $!;
open(DES,'>',$DES) or die $!;

my $n_fields = "";
while (<>) {
    s/\s+$//;
    if (/^\#/) { # header line
        my @t = split(/,/);
        $n_fields = scalar(@t); # total number of fields
    } else { # actual data
        my $n_commas = $_ =~s/,/,/g; # total number of commas
        foreach my $i (0 .. $n_commas - $n_fields) { # iterate ($n_commas - $n_fields + 1) times
            s/(\"[^",]+),([^"]+\")/$1\"$2/g; # single replacement per previous answers
        }
        s/\"//g; # removal of double quotes (if you want)
    }
    print DES "$_\n";
}

close (DES);
close (SRC);

我上面提供的代码可以工作,但是非常非常慢。正在寻找一种更快的方法来处理数据。比你提前。

voj3qocg

voj3qocg1#

我接的任务是:删除引号下的逗号,并删除引号。(因此,请清除CSV文件中多余的逗号并删除引号。)
对于初学者来说,这里有一些基于正则表达式的精简代码。我希望这比OP代码快得多。请提供一个示例输入(和/或时间代码)以进行改进。请注意清楚地采取的假设。

use warnings;
use strict;
use feature 'say';

my $file = shift // die "Usage: $0 file\n";

open my $fh, $file or die "Can't open $file: $!";

my $header = <$fh>;

if ($header !~ /^#/) {  # not actually a header but data! Process
    print $header =~ s{"[^"]+"}{ $& =~ tr/,"//dr }ger;  #"
}

while (<$fh>) {
    print s{"[^"]+"}{ $& =~ tr/,"//dr }ger;  #" stop bad syntax hilite
}

我们始终建议使用库(如Text::CSV,请参见here示例),但在这种情况下可能不会更快。(需要实际的样本来对CSV库进行基准测试。)†
[2]在等待一个现实的样本时,这里有一个基准。
我通过重复1,000次并打乱行来创建一个输入文件:

have,"text, and, commas","and more, commas",end
so,"no stuff with commas","yes remove, commas",done

然后在其上运行一个程序

use warnings;
use strict;
use feature 'say';

#use Test::More qw(no_plan);  # uncomment to run a test below
use Text::CSV; 
use Benchmark qw(cmpthese);

my $file = shift // die "Usage: $0 file\n";

my $runfor = shift // 3;

sub by_regex {
    my ($fh) = @_;    
    my @lines;

    my $header = <$fh>;
    if ($header !~ /^#/) { # not actually a header but data!
        push @lines, $header =~ s{"([^"]+)"}{ $1 =~ tr/,//dr }ger; #"
    }

    while (<$fh>) {
        push @lines, s{"([^"]+)"}{ $1 =~ tr/,//dr }ger;  #"
    }

    seek $fh, 0, 0;  # return filehandle to beginning for the next run

    chomp @lines;
    return \@lines;
};#]]

sub by_lib {#[[
    my ($csv, $fh) = @_;
    my @lines;

    my @headers = @{ $csv->getline($fh) };
    if ($headers[0] !~ /^#/) { # not headers! data
        tr/,//d for @$row; 
        push @lines, join ',', @$row;
    }

    while (my $row = $csv->getline($fh)) {  #
        tr/,//d for @$row;
        push @lines, join ',', @$row;
    }

    seek $fh, 0, 0;
    return \@lines;
}#]]

my $csv = Text::CSV->new( { binary => 1, allow_whitespace => 1 } ) 
    or die "Cannot use CSV: " . Text::CSV->error_diag (); 

open my $fh, $file or die $!;

# Check, at least once for every change in code
#is_deeply( by_regex($fh),  by_lib($csv, $fh), 'same' ); 

cmpthese( -$runfor, {
    by_regex => sub { by_regex($fh) },
    by_lib   => sub { by_lib($csv, $fh) },
});

如果您的输入使用了松散的引号,使some "then quoted" more之类的字段而不是整个字段被引用,则使用allow_loose_quotes属性。
通过运行program.pl input.csv 30(每个测试运行30秒),我得到,在一个旧的笔记本电脑与v5.16

Rate   by_lib by_regex
by_lib   130/s       --     -14%
by_regex 152/s      17%       --

(在5.36.0版本的服务器上几乎相同,速率大约是5.36.0版本的两倍。)
如果你的情况也是如此,并且regex不能得到实质性的改进,那么我强烈建议你使用CSV库的代码。CSV库的正确性和处理一系列意外/坏的CSV属性的能力远比20%的速度改进更有价值。
然后,至少对于非常大的文件,不要在数组中收集行(push @lines, join ',', @$row;),而是在它们出现时打印它们(say join ',', @$row;)。

ymdaylpp

ymdaylpp2#

这看起来像是您创建了一个无限循环,在阅读文件的同时写入文件。

my $SRC = $ARGV[0];     # files in your @ARGV will be read by <> diamond operator
my $DES = $ARGV[1];
....
while (<>) {            # should be <SRC>, but is <>
....
    print DES "$_\n";   # adding lines to your output file while reading it with <>

while (<>)更改为while (<SRC>),看看会发生什么情况。可能还要检查输出文件的文件大小,以确认是否存在表示无限循环的大文件。

相关问题