关于mmap,《Python v2.7.2 documentation》中的描述如下:
mmap — Memory-mapped file support
Memory-mapped file objects behave like both strings and like file objects. Unlike normal string objects, however, these are mutable. You can use mmap objects in most places where strings are expected; for example, you can use the re module to search through a memory-mapped file. Since they’re mutable, you can change a single character by doing obj[index] = 'a', or change a substring by assigning to a slice: obj[i1:i2] = '...'. You can also read and write data starting at the current file position, and seek() through the file to different positions.
在re+mmap实现大文件的正则匹配中,我采取“首先用mmap映射文件,然后用re匹配内容”的方法进行过大文件的正则匹配。这种方法确实有效。但是,当我将使用正则表达式从映射文件中提取到的匹配结果写入新文件时,用Vim打开新文件后,每一行的结尾处总有一个^M字符(0x0d)。当时觉得有点奇怪,不过想了想又以为可能是正则匹配的中间信息没有被删除掉,当时也不影响后续的文件操作,所以也没有继续研究。
最近因为要分析C程序中的宏的缘故,再次用到了re+mmap组合。待分析的头文件data.h的内容为:
#define LEN 2\n
按照逻辑,我编写了如下正则表达式用于匹配提取宏名及其对应的替换文本:
macro_regex = re.compile(r"\s*#\s*define\s*(\w+)\s*(.*?)[\n]")
以下代码中,分别使用macro_regex直接匹配文件内容,以及匹配mmap映射后的文件内容:
f = open('data.h', 'r') # 直接匹配文件内容 data = f.read() print macro_regex.findall(data) # 匹配mmap映射后的文件内容 data = mmap.mmap(f.fileno(), 0, access = mmap.ACCESS_READ) print macro_regex.findall(data) f.close()
所得的结果输出为:
[('LEN', '2')] [('LEN', '2\r')]
上述结果表明:mmap映射后的文件内容发生了变化。于是,我又直接比较了data.h的原始文件内容和mmap映射后的文件内容:
#define LEN 2\n ## 原始文件内容
#define LEN 2\r\n ## mmap映射后的文件内容
果然,mmap映射后的文件内容中,原来的'\n'变成了'\r\n'。我这才发现,原来之前Vim打开时看到的每行末尾的^M字符(0x0d)其实就是'\r'。我后来又做了一系列的测试(比如将mmap映射后的文件内容直接写入到另一个文件中),证实了确实存在这种差异。
“ 使用mmap映射后,文件中的'\n'变成了'\r\n' ”
这种差异非常隐晦,一般情况下很难预料;但这种差异的影响却显而易见:如果使用mmap映射后的文件内容已经发生了变化,那么以原始的文件内容为依据的所有假设和逻辑都可能会出现混乱。
值得一提的是,这种情况是我在Windows下面使用Python2.7时遇到的,其他系统平台和Python版本下的情况我没有验证过。在此做一个记录,以备后续回顾与参考。