使用Perl解析文本文件的最有效方法是什么?

时间:2011-03-02 20:59:31

标签: perl parsing optimization field text-files

虽然这是非常基本的,但我找不到类似的问题,所以如果你知道现有的问题/解决方案,请链接到一个。


我有一个大约2MB且大约16,000行的.txt文件。每个记录长度为160个字符,阻塞因子为10.这是一种较旧的数据结构类型,几乎看起来像制表符分隔文件,但分隔是单字符/空格。

首先,我glob .txt个文件的目录 - 目录中一次只有一个文件,因此这种尝试本身可能效率低下。

my $txt_file = glob "/some/cheese/dir/*.txt";

然后我用这一行打开文件:

open (F, $txt_file) || die ("Could not open $txt_file");

根据此文件的数据字典,我在while循环中使用Perl的substr()函数解析每行中的每个“字段”。

while ($line = <F>)
{
$nom_stat   = substr($line,0,1);
$lname      = substr($line,1,15);
$fname      = substr($line,16,15);
$mname      = substr($line,31,1);
$address    = substr($line,32,30);
$city       = substr($line,62,20);
$st         = substr($line,82,2);
$zip        = substr($line,84,5);
$lnum       = substr($line,93,9);
$cl_rank    = substr($line,108,4);
$ceeb       = substr($line,112,6);
$county     = substr($line,118,2);
$sex        = substr($line,120,1);
$grant_type = substr($line,121,1);
$int_major  = substr($line,122,3);
$acad_idx   = substr($line,125,3);
$gpa        = substr($line,128,5);
$hs_cl_size = substr($line,135,4);
}

<小时/> 这种方法需要花费大量时间来处理每一行,我想知道是否有更有效的方法从文件的每一行中获取每个字段。

任何人都可以提出更有效/首选的方法吗?

4 个答案:

答案 0 :(得分:8)

在我看来,您正在使用固定宽度字段。真的吗?如果是,unpack功能就是您所需要的。您提供字段的模板,它将从这些字段中提取信息。有tutorial可用,模板信息可在pack的文档中找到,它是unpack的逻辑逆。简单来说就是一个基本的例子:

my @values = unpack("A1 A15 A15 ...", $line);

其中'A'表示任何文本字符(据我所知),数字是多少。有些人使用它,unpack有很多艺术,但我相信这足以满足基本用途。

答案 1 :(得分:4)

使用/o选项编译和缓存的单个正则表达式是最快的方法。我使用Benchmark模块以三种方式运行您的代码,并提出:

         Rate unpack substr regexp
 unpack 2.59/s     --   -59%   -67%
 substr 6.23/s   141%     --   -21%
 regexp 7.90/s   206%    27%     --

输入是一个包含20k行的文件,每行有160个字符(16个字符0123456789重复)。因此,它与您正在使用的数据的输入大小相同。

Benchmark::cmpthese()方法将子程序调用从最慢​​输出到最快。第一列告诉我们每秒可以运行多少次子程序。 正则表达式方法最快。不解压缩,因为我之前说过。对不起。

基准代码如下。打印报表作为健全性检查。这是为darwin-thread-multi-2level构建的Perl 5.10.0。

#!/usr/bin/env perl
use Benchmark qw(:all);
use strict;

sub use_substr() {
    print "use_substr(): New itteration\n";
    open(F, "<data.txt") or die $!;
    while (my $line = <F>) {
        my($nom_stat, 
           $lname,   
           $fname,      
           $mname,    
           $address,     
           $city,    
           $st,       
           $zip,         
           $lnum,        
           $cl_rank,
           $ceeb,    
           $county,
           $sex,     
           $grant_type,
           $int_major, 
           $acad_idx,  
           $gpa,   
           $hs_cl_size) = (substr($line,0,1),
                           substr($line,1,15),
                           substr($line,16,15),
                           substr($line,31,1),
                           substr($line,32,30),
                           substr($line,62,20),
                           substr($line,82,2),
                           substr($line,84,5),
                           substr($line,93,9),
                           substr($line,108,4),
                           substr($line,112,6),
                           substr($line,118,2),
                           substr($line,120,1),
                           substr($line,121,1),
                           substr($line,122,3),
                           substr($line,125,3),
                           substr($line,128,5),
                           substr($line,135,4));
       #print "use_substr(): \$lname = $lname\n";
       #print "use_substr(): \$gpa   = $gpa\n";
    }    
    close(F);
    return 1;
}

sub use_regexp() {
    print "use_regexp(): New itteration\n";
    my $pattern = '^(.{1})(.{15})(.{15})(.{1})(.{30})(.{20})(.{2})(.{5})(.{9})(.{4})(.{6})(.{2})(.{1})(.{1})(.{3})(.{3})(.{5})(.{4})';
    open(F, "<data.txt") or die $!;
    while (my $line = <F>) {
        if ( $line =~ m/$pattern/o ) {
            my($nom_stat, 
               $lname,   
               $fname,      
               $mname,    
               $address,     
               $city,    
               $st,       
               $zip,         
               $lnum,        
               $cl_rank,
               $ceeb,    
               $county,
               $sex,     
               $grant_type,
               $int_major, 
               $acad_idx,  
               $gpa,   
               $hs_cl_size) = ( $1,
                                $2,
                                $3,
                                $4,
                                $5,
                                $6,
                                $7,
                                $8,
                                $9,
                                $10,
                                $11,
                                $12,
                                $13,
                                $14,
                                $15,
                                $16,
                                $17,
                                $18);
            #print "use_regexp(): \$lname = $lname\n";
            #print "use_regexp(): \$gpa   = $gpa\n";
        }
    }    
    close(F);
    return 1;
}

sub use_unpack() {
    print "use_unpack(): New itteration\n";
    open(F, "<data.txt") or die $!;
    while (my $line = <F>) {
        my($nom_stat, 
           $lname,   
           $fname,      
           $mname,    
           $address,     
           $city,    
           $st,       
           $zip,         
           $lnum,        
           $cl_rank,
           $ceeb,    
           $county,
           $sex,     
           $grant_type,
           $int_major, 
           $acad_idx,  
           $gpa,   
           $hs_cl_size) = unpack(
               "(A1)(A15)(A15)(A1)(A30)(A20)(A2)(A5)(A9)(A4)(A6)(A2)(A1)(A1)(A3)(A3)(A5)(A4)(A*)", $line
               );
        #print "use_unpack(): \$lname = $lname\n";
        #print "use_unpack(): \$gpa   = $gpa\n";
    }
    close(F);
    return 1;
}

# Benchmark it
my $itt = 50;
cmpthese($itt, {
        'substr' => sub { use_substr(); },
        'regexp' => sub { use_regexp(); },
        'unpack' => sub { use_unpack(); },
    }
);
exit(0)

答案 2 :(得分:0)

在每一行上进行拆分,如下所示:

my @values = split(/\s/,$line);

然后使用您的价值观。

答案 3 :(得分:0)

您可以执行以下操作:

while ($line = <F>){
   if ($line =~ /(.{1}) (.{15}) ........ /){
     $nom_stat = $1;
     $lname = $2;
     ...
   }
}

我认为它比你的substr建议更快,但我不确定它是否是最快的解决方案,但我认为它可能很好。