【问题标题】:In Perl, can I limit the length of a line as I read it in from a file (like fgets)?在 Perl 中,我可以在从文件(如 fgets)中读取行时限制行的长度吗?
【发布时间】:2010-05-30 15:03:01
【问题描述】:

我正在尝试编写一段代码,它逐行读取文件并存储每一行​​,最多可存储一定数量的输入数据。除了防止吸入异常大的文件外,我还想防止最终用户作恶并在一行上放置类似演出的数据。执行$str = <FILE> 仍然会读一整行,这可能会很长并且会破坏我的记忆。

fgets 让我通过让我在每次调用期间指定要读取的字节数来做到这一点,并且基本上让我将一根长线分成我的最大长度。在 perl 中是否有类似的方法可以做到这一点?我看到了一些关于 sv_gets 的东西,但不知道如何使用它(虽然我只是粗略地在 Google 上搜索了一下)。

本练习的目标是避免在读取数据后进行额外的解析/缓冲。 fgets 在 N 个字节后或到达换行符时停止。

编辑我想我有些困惑。我想读取 X 行,每行的最大长度为 Y。我不想读取超过 Z 字节的总数,并且我不想一次读取所有 Z 字节。我想我可以这样做并拆分线路,但想知道是否还有其他方式。如果这是最好的方法,那么使用读取功能并进行手动解析是我最简单的选择。

谢谢。

【问题讨论】:

  • 为什么不希望一次读取所有 Z 字节?您在寻找 get_n_lines_or_max_bytes(fh, n, z) 函数吗?这样的野兽并不难写……
  • 我想这只是一个偏好问题。当我可以增量解析时,我讨厌吸入大量数据。另外,我也会忽略一些数据,所以为什么要先占用不必要的内存。我认为这是一种易于维护/编写的解决方案。
  • @SB:测试一下。你会发现调用 read() 和 split() 比使用任何 fgets() 实现使用更少的内存并且运行得更快。

标签: perl fgets


【解决方案1】:

Perl 没有内置 fget,但 File::GetLineMaxLength 实现了它。

如果你想自己做,getc 非常简单。

sub fgets {
    my($fh, $limit) = @_;

    my($char, $str);
    for(1..$limit) {
        my $char = getc $fh;
        last unless defined $char;
        $str .= $char;
        last if $char eq "\n";
    }

    return $str;
}

将每个字符连接到$str 是有效的,因为 Perl 会机会主义地重新分配。如果一个 Perl 字符串有 16 个字节并且你连接另一个字符,Perl 会将它重新分配到 32 个字节(32 到 64、64 到 128...)并记住长度。接下来的 15 个连接不需要重新分配内存或调用 strlen。

【讨论】:

  • 我认为这很干净,我看到您的另一个答案是讨论在 Perl 中预分配字符串。将两者结合起来可以消除常量重新分配的低效率(如果有的话),因为我只需要分配一次最大长度。
  • 谢谢。我认为预分配不会给您带来太多收益。事实上,它可能会更慢,因为在 Perl 中预分配字符串可能比让 perl 来做要慢。您还将浪费大量内存,因为每个字符串都将使用最大内存。基准测试证明了这一点。如果您真的希望它尽可能快,请在 fgets() 周围编写一个 XS 包装器。它相当微不足道(按 XS 标准)。
  • 我的意思是在对 fgets 的调用之外预先分配字符串,并通过引用您的 fgets 来附加到。虽然不确定当我将字符串分配给另一个时会发生什么。我还不如让它自己分配
  • @SB 我试过了,它慢了大约 5%。我的猜测是循环内的取消引用比您在预分配中节省的速度更慢。像 geocar 那样使用 $_[2] 的别名也无济于事(没有伤害)。 Perl 优化的经验法则是你不能用 Perl 打败 perl。您可以在此处查看基准程序:gist.github.com/417919 我认为您不会通过微优化来提高速度,在 Perl 中循环文件中的每个字符只会产生一定的开销。跨度>
  • @j_random_hacker 嗯,这不是 5%,而是界面更差的那个并没有更快。
【解决方案2】:
sub heres_what_id_do($$) {
    my ($fh, $len) = @_;
    my $buf = '';

    for (my $i = 0; $i < $len; ++$i) {
        my $ch = getc $fh;
        last if !defined $ch || $ch eq "\n";
        $buf .= $ch;
    }

    return $buf;
}

不是很“Perlish”,但谁在乎呢? :) 操作系统(可能还有 Perl 本身)将在下面进行所有必要的缓冲。

【讨论】:

  • == '\n' 应该是 eq "\n"getc 比使用 read 获取单个字符要简单得多。基准测试显示它比我的慢约 15%。有趣的是,3 arg for 比 for my $i (0..$len-1) 快得多,但不比 my $i; my $end = $len-1; for $i (0..$len) 快(它与我的相当)表明 Perl 的 for(0..$foo) 迭代器优化很容易被击败。
  • 感谢 Schwern 的编辑。这很尴尬,但我不知道 Perl 实际上有 getc()!将编辑以使用它。
【解决方案3】:

作为练习,我实现了一个围绕 C 的 fgets() 函数的包装器。对于定义为“没有文件号的任何东西”的复杂文件句柄,它回退到 Perl 实现,以涵盖绑定句柄和诸如此类的东西。 File::fgets 正在前往 CPAN 的途中,您可以从存储库中提取副本。

一些基本的基准测试显示它比这里的任何实现都快 10 倍以上。但是,我不能说它没有错误或不会泄漏内存,我的 XS 技能不是那么好,但它比这里的任何东西都经过了更好的测试。

【讨论】:

    【解决方案4】:

    使用read function(perlfunc 读取)

    【讨论】:

    • fgets 的美妙之处在于它要么读取 N 条数据,要么在换行处停止。我认为 read 不会在换行符处停止。
    【解决方案5】:

    您可以自己轻松实现fgets()。这是works like C

    sub fgets{my($n,$c)=($_[1],''); ($_[0])=('');
      for(;defined($c)&&$c ne "\n"&&$n>0;$n--){$_[0].=($c=getc($_[2]));}
      defined($c)&&$_[0]; }
    

    这是一个带有 PHP 的 semantics:

    sub fgets{my($n,$c,$x)=($_[1],'','');
      for(;defined($c)&&$c ne "\n"&&$n>0;$n--){$x.=($c=getc($_[0]));}
      ($x ne '')&&$x; }
    

    如果您尝试实施资源限制(即尝试防止不受信任的客户端占用您的所有内存),您真的不应该这样做。在调用脚本之前使用ulimit 设置这些资源限制。一个好的系统管理员无论如何都会设置资源限制,但他们喜欢程序员编写设置合理限制的启动脚本。

    如果您在将此数据代理到另一个站点之前尝试限制输入(例如,限制 SMTP 输入行,因为您知道远程站点可能不支持超过 511 个字符),那么只需检查 @ 之后的行长度987654327@ 和length()

    【讨论】:

    • 无法...理解...代码!它会在 eof 处引发警告,因为它会在检查 $c 是否已定义之前进行连接。虽然它非常令人钦佩地反映了 C 的 fgets,但它并不是很 Perlish。尽管它的所有不可理解性,它并不比我的或 j_random 的快。
    • @Schwem:如果您对此感到困扰,那就no strict
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-12-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多