首页 > 解决方案 > 如何使用 File::Map 正确写入文件?

问题描述

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

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

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

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

将新值截断为内存映射的大小

对这两个警告的解释读起来就像一个人不应该使用 写任何东西File::Map,但它可能适用于可以使用截断文件或根本没有改变整体文件大小的情况。但是第一个引用明确提到支持写入,该规则没有任何例外。

那么,是否有一些特殊的方法可以安全地使用 using 进行编写File::Map,例如增加底层文件等?第一个警告使用了措辞directly,我觉得还有其他更好的支持方式来编写?

=~ s///目前只是在映射视图上使用,这似乎是错误的方法。我什至找不到任何人尝试使用 using 进行编写File::Map,只有官方测试完全符合我的要求,并期待我收到警告。此外,查看代码,似乎只有一个用例根本不会导致警告,尽管我不明白我是如何触发它的:

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

毕竟,如果完全应该避免写作,为什么文档明确提到它是受支持的?如果它至少在所有情况下都导致警告,但对我来说似乎不受支持。

标签: perlmemory-mapped-files

解决方案


mmap 是文件的一部分到内存的固定大小的映射。

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

使用 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 );[1]唯一的缺点是这样做需要使用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

我已经提交了一张票


  1. 这有点讽刺,因为它在不使用时或多或少会发出警告substr,但我想它在substr“不正确”使用时也会发出警告。

推荐阅读