通常在UNIX下面处理文本文件的方法是sed、awk等shell命令,对于处理大文件受CPU,IO等因素影响,对服务器也有一定的压力。关于sed的说明可以看了解sed的工作原理,本文将介绍通过python的mmap模块来实现对大文件的处理,来对比看他们的差异。
mmap是一种虚拟内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。关于系统中mmap的理论说明可以看百度百科和维基百科说明以及mmap函数介绍,这里的说明是针对在Python下mmap模块的使用说明。
使用:
1,创建:创建并返回一个 mmap 对象m
fileno: 文件描述符,可以是file对象的fileno()方法,或者来自os.open(),在调用mmap()之前打开文件,不再需要文件时要关闭。

View Code
length:要映射文件部分的大小(以字节为单位),这个值为0,则映射整个文件,如果大小大于文件当前大小,则扩展这个文件。
flags:MAP_PRIVATE:这段内存映射只有本进程可用;mmap.MAP_SHARED:将内存映射和其他进程共享,所有映射了同一文件的进程,都能够看到其中一个所做的更改;
prot:mmap.PROT_READ, mmap.PROT_WRITE 和 mmap.PROT_WRITE | mmap.PROT_READ。最后一者的含义是同时可读可写。
access:在mmap中有可选参数access的值有
ACCESS_READ:读访问。
ACCESS_WRITE:写访问,默认。
ACCESS_COPY:拷贝访问,不会把更改写入到文件,使用flush把更改写到文件。
2,方法:mmap 对象的方法,对象m
方法的使用说明:介绍上面常用的方法
测试文本:test.txt,mmap对象m

①: m.close(),关闭对象
②:m.find(str, start=0),从start的位置开始寻找第一次出现的str。
③:m.read(n),返回一个从 m对象文件中读取的n个字节的字符串,将会把 m 对象的位置指针向后移动,后续读取会继续往下读。
④:m.read_byte(),返回一个1字节长的字符串,从 m 对应的文件中读1个字节
⑤:m.readline():返回一个字符串,从 m 对应文件的当前位置到下一个'\n',当调用 readline() 时文件位于 EOF,则返回空字符串
⑥:m.size():返回 m 对应文件的长度(不是 m 对象的长度len(m))
⑦:m.tell():返回 m 对应文件的当前光标位置
⑧:m.seek(pos, how=0),改变 m 对应的文件的当前位置
⑨:m.move(dstoff, srcoff, n):等于 m[dstoff:dstoff+n] = m[srcoff:srcoff+n],把从 srcoff 开始的 n 个字节复制到从 dstoff 开始的n个字节
⑩:m.write(str):把 str 写到 m 对应文件的当前光标位置(覆盖对应长度),如果从 m 对应文件的当前光标位置到 m 结尾剩余的空间不足len(str),则抛出 ValueError
⑪:m.flush():把 m 中从offset开始的n个字节刷到对应的文件中
注意:对于m的修改操作,可以当成一个列表进行切片操作,但是对于切片操作的修改需要改成同样长度的字符串,否则都会报错。如m中的10个字符串进行修改,必须改成10个字符的长度。
3,应用说明:
1):读文件,ACCESS_READ
①:读取整个文件
效果:

②:逐步读取指定字节数文件

2):查找文件,ACCESS_READ
①:从整个文件查找所有匹配的

②:从整个文件里查找,找到就退出(确认到底是否存在)

③:通过正则查找,(找出40开头的数字)

View Code
3):处理文本,只能等长处理(通过上面的查找方法,来替换查找出的内容),模式:ACCESS_WRITE、ACCESS_COPY
经过上面对mmap方法的介绍和使用说明,大致了解了mmap的特点。这里通过对比sed的方法,来看看到底处理大文件使用哪种方法更高效。
①:替换文本中出现一次的内容。比如想把A库的备份文件(9G)还原到B库,需要把里面的USE `A`改成USE `B`。
1> sed处理:时间消耗近105s;磁盘IO几乎跑满;内存几乎没消耗、CPU消耗10~20%之间。
IO消耗:

2> python处理:时间消耗是毫秒级别的,几乎是秒级别完成,该情况比较特别:搜索的关键词在大文本里比较靠前的位置,这样处理上T的大文件也是非常快的,要是搜索的关键词靠后怎会怎么样呢?后面会说明。
执行:
②:替换文本中所有匹配的关键词。比如想把备份文件里的ENGINE=MYISAM改成ENGINE=InnoDB,看看性能如何。
1> sed处理:时间消耗110s;磁盘IO几乎跑满(读写IO高);内存几乎没消耗、CPU消耗10~30%之间。
和①中sed的执行效果差不多,其实对于处理一条还是多条记录,sed都是做同样工作量的事情,至于原因可以看了解sed的工作原理说明,个人理解大致意思就是:sed是1行1行读取(所以内存消耗很小),放入到自己设置的缓冲区里,替换完之后再写入(所以IO很高),处理速度受限于CPU和IO。
2> python处理:时间消耗20多秒,比sed少。因为不用重写所有内容,只需要替换指定的内容即可,并且是在内存中处理的,所以写IO的压力几乎没有。当关键词比较靠后,其读入的数据就比较大,文件需要从磁盘读入到内存,这时磁盘的读IO也很高,写IO还是没有。因为是虚拟内存映射文件,所以占用的物理内存不多,虽然通过TOP看到的内存使用率%mem很高,这里可以不用管,因为大部分都是在SHR列里的消耗,真正使用掉的内存可以通过RES-SHR来计算。关于top中SHR的意思,可以去看相关文章说明。
③:正则匹配修改,这个可以通过上面介绍的查找方法,做下修改即可,就不再做说明。
小结:
对比sed和python处理文件的方法,这里来小结下:对于sed不管修改的关键字在文本中的任意位置、次数,修改的工作量都一样(全文的读写IO),差距不大;对于python mmap的修改,要是关键字出现在比较靠前的地方,修改起来速度非常快,否则修改也会有大量的读IO,写IO没有。通过上面的对比分析来看,mmap的修改要比sed修改性能高。
Python还有另一个读取操作的方法:open中的read、readline、readlines,这个方法是把文件全部载入内存,再进行操作。若内存不足直接用swap或则报错退出,内存消耗和文本大小成正比,而通过mmap模块的方法可以很好的避免了这个问题。
通过上面的介绍,大致知道如何使用mmap模块了,其大致特点如下:
普通文件被映射到虚拟地址空间后,程序可以向访问普通内存一样对文件进行访问,在有些情况下可以提高IO效率。
它占用物理内存空间少,可以解决内存空间不足的问题,适合处理超大文件。
不同于通常的字符串对象,它是可变的,可以通过切片的方式更改,也可以定位当前文件位置m.tell()或m.seek()定位到文件的指定位置,再进行m.write(str)固定长度的修改操作。
最后,可以把mmap封装起来进行使用了,脚本信息:

方法:

脚本处理效果:(40G的文本)
结论:修改大文本文件,通过sed处理,不管被修改的词在哪个位置都需要重写整个文件;而mmap修改文本,被修改的词越靠前性能越好,不需要重写整个文本,只要替换被修改词语的长度即可。
Memory-mapped file support
通过mmap库映射文件到内存用法
mmap模块与mmap对象
~~~~~~~~~~~~~~~
万物之中,希望至美