代码之家  ›  专栏  ›  技术社区  ›  Thorsten Schöning

如何使用file::Map正确写入文件?

  •  8
  • Thorsten Schöning  · 技术社区  · 7 年前

    我正在使用 File::Map 通常用于将特别小的文本文件映射到内存中,例如,在这些文件上处理一些只读正则表达式。现在我有了一个用例,在这个用例中,我还需要替换文件中的一些文本,并且认为我仍然可以使用 File::Map ,因为它记录了以下内容:

    文件被映射到一个变量中,该变量可以像任何其他变量一样读取,并且可以使用标准的Perl技术(如regexps和substr)将其写入。

    虽然我感兴趣要替换的数据在文件中被正确替换,但我正在丢失数据,因为文件保持其原始大小,并且数据最终被截断。新数据比旧数据大一点。这两件事都要用以下句子加以警告:

    不建议直接写入内存映射文件

    对这两个警告的解释读起来像是一个人永远不应该使用 文件::映射 ,但它可能在以下情况下起作用:可以使用被截断的文件,或者根本不改变文件的总体大小。但是第一句话明确提到了writes作为受支持的,这条规则没有任何例外。

    文件::映射 ,例如,增加基础文件等?第一条警告使用了以下措辞: directly ,我觉得还有其他更好的写作方法?

    我只是在用 =~ s/// 在当前的映射视图上,这似乎是错误的方法。我甚至找不到任何人试图使用 文件::映射

    static int mmap_write(pTHX_ SV* var, MAGIC* magic) {
            struct mmap_info* info = (struct mmap_info*) magic->mg_ptr;
            if (!SvOK(var))
                    mmap_fixup(aTHX_ var, info, NULL, 0);
            else if (!SvPOK(var)) {
                    STRLEN len;
                    const char* string = SvPV(var, len);
                    mmap_fixup(aTHX_ var, info, string, len);
            }
            else if (SvPVX(var) != info->fake_address)
                    mmap_fixup(aTHX_ var, info, SvPVX(var), SvCUR(var));
            else
                    SvPOK_only_UTF8(var);
            return 0;
    }
    

    https://metacpan.org/source/LEONT/File-Map-0.55/lib/File/Map.xs#L240

    毕竟,如果应该避免书写,为什么文档会明确提到它是受支持的?如果它在所有情况下都至少导致一个警告(只有一个除外),我看起来不支持它。

    2 回复  |  直到 5 年前
        1
  •  8
  •   ikegami Gilles Quénot    7 年前

    各种映射函数将所提供标量的字符串缓冲区设置为映射内存页。操作系统会将对该缓冲区的任何更改反映到文件中,反之亦然。

    使用mmap的正确方法是 修改 字符串缓冲区,而不是替换它。

    • $ perl -e'print "\0"x16' >scratch
      
      $ perl -MFile::Map=map_file -we'
         map_file my $map, "scratch", "+<";
         $map =~ s/\x00/\xFF/g;             # ok
         substr($map, 6, 2, "00");          # ok
         substr($map, 8, 2) = "11";         # ok
         substr($map, 7, 2) =~ s/../22/;    # ok
      '
      
      $ hexdump -C scratch
      00000000  ff ff ff ff ff ff 30 32  32 31 ff ff ff ff ff ff  |......0221......|
      00000010
      
    • 任何替换字符串缓冲区的操作(例如分配给标量)都不正常。

      …有点。模块注意到您已替换标量的缓冲区。它继续将新缓冲区的内容复制到映射内存,然后用指向映射内存的指针替换标量缓冲区。

      $ perl -e'print "\0"x16' >scratch
      
      $ perl -MFile::Map=map_file -we'
         map_file my $map, "scratch", "+<";
         $map = "4" x 16;  # Effectively: substr($map, 0, 16, "4" x 16)
      '
      Writing directly to a memory mapped file is not recommended at -e line 3.
      
      $ hexdump -C scratch
      00000000  34 34 34 34 34 34 34 34  34 34 34 34 34 34 34 34  |4444444444444444|
      00000010
      

      no warnings qw( substr ); , 唯一的缺点是这样做需要使用 memcpy 抄袭 length($map) 字节,而使用 substr($map, $pos, length($repl), $repl) 只需要复制 length($repl)

    • $ perl -MFile::Map=map_file -we'
         map_file my $map, "scratch", "+<";
         $map = "5" x 32;  # Effectively: substr($map, 0, 16, "5" x 16)
      '
      Writing directly to a memory mapped file is not recommended at -e line 3.
      Truncating new value to size of the memory map at -e line 3.
      
      $ hexdump -C scratch
      00000000  35 35 35 35 35 35 35 35  35 35 35 35 35 35 35 35  |5555555555555555|
      00000010
      

    警告:如果收缩缓冲区,模块不会发出警告,即使这没有任何效果,只是用NUL对其中一个字节进行重击。

    $ perl -e'print "\0"x16' >scratch
    
    $ perl -MFile::Map=map_file -we'
       map_file my $map, "scratch", "+<";
       substr($map, 0, 16, "6" x 16);
       substr($map, 14, 2, "");
    '
    
    $ hexdump -C scratch
    00000000  36 36 36 36 36 36 36 36  36 36 36 36 36 36 00 36  |66666666666666.6|
    00000010
    

    ticket .


    1. substr substr “不正确”。
        2
  •  6
  •   Community Mohan Dere    5 年前

    first quote ,

    文件被映射到一个变量中,该变量可以像任何其他变量一样读取,并且可以使用标准的Perl技术(如regexps和 substr .

    在“简单性”标题下。

    Warnings 我们有:

    由于perl在内部的工作方式,不可能编写一个允许直接赋值但仍能很好执行的映射实现。作为一种折衷方案,如果您仍然这样做,File::Map能够修复混乱,但它会警告您正在做不应该做的事情。此警告仅在以下情况下发出: use warnings 'substr' 这是有效的。

    也就是说,除非可以在适当的位置修改字符串缓冲区,否则通过mmap'd变量进行写入是没有效率的(字符串必须首先组装并存储在内存中,然后才复制到文件中)。如果您对此满意,可以使用禁用警告 no warnings 'substr' .

    此外,看看代码,似乎只有一个用例在编写时根本不会导致警告,尽管我不明白如何触发警告。

    在这种情况下,您试图将缓冲区写入自身。当一个标量实际上被就地修改时,就会发生这种情况。其他情况是替换字符串缓冲区时的变通方法(例如,因为它被覆盖: $foo = $bar ). 对于真正的就地修改,不需要额外的工作,也不会收到警告。

    但这对您没有帮助,因为不能使用固定大小的映射缓冲区就地增长字符串。

    无法更改文件的大小。这不是因为File::Map,而是因为底层 mmap system call

    如果需要编辑文件(尤其是小文件),我建议使用 edit in Path::Tiny 相反

    推荐文章