在perl中读取二进制文件 - 耗尽内存

时间:2017-02-15 10:33:06

标签: perl memory binary filehandle

我想读取一个大的二进制文件(500MB)并获取位于每5000字节重复一次的标头之后的特定字节。为此,我有一个简短的片段,以二进制模式读取文件,块大小为16536。

代码按预期工作,但会耗尽所有可用内存,使其无法使用。我尝试关闭并打开我每次写入操作时写入的解析输出文件,但这没有用。问题可能与我阅读二进制文件的方式有关吗?

这是我的代码:

use strict;

my $BLOCK_SIZE=16536;

my $fname = $ARGV[0];
my $fparsename = $ARGV[1];
open(F,"<$fname") or die("Unable to open file $fname, $!");
binmode(F);
my $buf;
my $ct=0;
my $byte=0;
my $byte_old=0;
my $byte_cnt=0;
my $byte_lock=0;
my $sample_msb=0;
my $sample_lsb=0;
my $sample_16b=0;
my $out_form='';

open(my $fh, '>', $fparsename) or die "Could not open file '$fparsename' $!";
print $fh ("Sample, Value \n");
close($fh);

while(read(F,$buf,$BLOCK_SIZE,$ct*$BLOCK_SIZE)){
    foreach(split(//, $buf)){

        $byte_old = $byte;
        $byte = ord($_);    # fetch byte (in decimal)


        if (($byte_old == 202) && ($byte == 254)) { # CA = 202, FE = 254
            $byte_cnt = 0;
            $byte_lock = 1;
        }

        if ($byte_lock == 1) {
            $byte_cnt++;
        }

        if ($byte_cnt == 20) {  # 20th byte after CAFE in header
            $sample_msb = $byte;
        }
        if ($byte_cnt == 21) {  # 21th byte after CAFE in header
            $sample_lsb = $byte;
        }

        if (($byte_cnt == 21) && ($byte_lock == 1)) {   # lock down and concatenate
            $byte_lock = 0;
            $byte_cnt = 0;
            $sample_16b = sprintf("%X", $sample_msb) . sprintf("%X", $sample_lsb);
            $out_form = sprintf("%d, %s \n", $ct++, $sample_16b);

            open(my $fh, '>>', $fparsename) or die "Could not open file '$fparsename' $!";
            printf $fh $out_form;
            close($fh);
        }

    }
    $ct++;

}
close(F);
close($fh);

1 个答案:

答案 0 :(得分:4)

嗯,首先,吃内存的东西是记忆中的东西。关闭和打开文件句柄没有任何区别,因为将根据需要刷新写入。

我无法重现您的问题,因为我没有合适的示例文件。

但是我觉得你的问题是你的read错了:

  

可以指定OFFSET将读取数据放在字符串中除开头之外的某个位置。负OFFSET指定从字符串末尾向后计数的多个字符的位置。大于SCALAR长度的正OFFSET会导致字符串在附加读取结果之前用“\ 0”字节填充到所需大小。

所以...从文件中读取'last',将创建一个长度为500MB的字符串,大部分为空字节。 (出于各种原因,它可能会超过500MB的实际内存)

只需省略read中的偏移量即可解决此问题。

但失败了:

所以我会仔细研究你的变量 - 特别是你将它们全部放在循环之外,并且意味着它们可能被连接,而不是重写。

我还建议,如果您按字节迭代文件,可以通过设置$/来大大简化。 E.g。

local $/ = \16536

while ( my $buf = <$input> ) { 

}

来自perlvar

  

将$ /设置为对整数的引用,包含整数的标量或可转换为整数的标量将尝试读取记录而不是行,最大记录大小为引用的整数字符数。

(或者如评论中所述 - 假设你的'块'是5000字节,那么设置local $/ = \5000可能是明智的。

我还建议 - 使用3 arg open,而不是2 arg:

open ( my $input, '<:raw', $fname ) or die $!;