在 perl 中读取二进制文件 - 运行 内存不足

Reading a binary file in perl - running out of memory

我想读取一个大型二进制文件 (500MB) 并获取位于 header 之后的特定字节,该文件每 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);

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

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

不过我认为你的问题是你 read 错了:

An OFFSET may be specified to place the read data at some place in the string other than the beginning. A negative OFFSET specifies placement at that many characters counting backwards from the end of the string. A positive OFFSET greater than the length of SCALAR results in the string being padded to the required size with "[=17=]" bytes before the result of the read is appended.

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

最简单的解决方法是省略 read 中的偏移量。

但失败了:

所以我会仔细查看您的变量 - 特别是,您将它们全部限定在循环之外,这意味着它们 可能 被连接起来,而不是被重写。

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

local $/ = 536

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

}

来自perlvar

Setting $/ to a reference to an integer, scalar containing an integer, or scalar that's convertible to an integer will attempt to read records instead of lines, with the maximum record size being the referenced integer number of characters.

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

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

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