背景:
通常在UNIX下面处理文本文件的方法是sed、awk等shell命令,对于处理大文件受CPU,IO等因素影响,对服务器也有一定的压力。关于sed的说明可以看了解sed的工作原理,本文将介绍通过python的mmap模块来实现对大文件的处理,来对比看他们的差异。
说明:
mmap是一种虚拟内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。关于系统中mmap的理论说明可以看百度百科和维基百科说明以及mmap函数介绍,这里的说明是针对在Python下mmap模块的使用说明。
使用:
1,创建:创建并返回一个 mmap 对象m
m=mmap.mmap(fileno, length[, flags[, prot[, access[, offset]]]])
fileno: 文件描述符,可以是file对象的fileno()方法,或者来自os.open(),在调用mmap()之前打开文件,不再需要文件时要关闭。
os.O_RDONLY 以只读的方式打开 Read only os.O_WRONLY 以只写的方式打开 Write only os.O_RDWR 以读写的方式打开 Read and write os.O_APPEND 以追加的方式打开 os.O_CREAT 创建并打开一个新文件 os.O_EXCL os.O_CREAT| os.O_EXCL 如果指定的文件存在,返回错误 os.O_TRUNC 打开一个文件并截断它的长度为零(必须有写权限) os.O_BINARY 以二进制模式打开文件(不转换) os.O_NOINHERIT 阻止创建一个共享的文件描述符 os.O_SHORT_LIVED os.O_TEMPORARY 与O_CREAT一起创建临时文件 os.O_RANDOM 缓存优化,但不限制从磁盘中随机存取 os.O_SEQUENTIAL 缓存优化,但不限制从磁盘中序列存取 os.O_TEXT 以文本的模式打开文件(转换)
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
m.close() 关闭 m 对应的文件; m.find(str, start=0) 从 start 下标开始,在 m 中从左往右寻找子串 str 最早出现的下标; m.flush([offset, n]) 把 m 中从offset开始的n个字节刷到对应的文件中; m.move(dstoff, srcoff, n) 等于 m[dstoff:dstoff+n] = m[srcoff:srcoff+n],把从 srcoff 开始的 n 个字节复制到从 dstoff 开始的n个字节,可能会覆盖重叠的部分。 m.read(n) 返回一个字符串,从 m 对应的文件中最多读取 n 个字节,将会把 m 对应文件的位置指针向后移动; m.read_byte() 返回一个1字节长的字符串,从 m 对应的文件中读1个字节,要是已经到了EOF还调用 read_byte(),则抛出异常 ValueError; m.readline() 返回一个字符串,从 m 对应文件的当前位置到下一个' ',当调用 readline() 时文件位于 EOF,则返回空字符串; m.resize(n) ***有问题,执行不了*** 把 m 的长度改为 n,m 的长度和 m 对应文件的长度是独立的; m.seek(pos, how=0) 同 file 对象的 seek 操作,改变 m 对应的文件的当前位置; m.size() 返回 m 对应文件的长度(不是 m 对象的长度len(m)); m.tell() 返回 m 对应文件的当前位置; m.write(str) 把 str 写到 m 对应文件的当前位置,如果从 m 对应文件的当前位置到 m 结尾剩余的空间不足len(str),则抛出 ValueError; m.write_byte(byte) 把1个字节(对应一个字符)写到 m 对应文件的当前位置,实际上 m.write_byte(ch) 等于 m.write(ch)。如果 m 对应文件的当前位置在 m 的结尾,也就是 m 对应文件的
当前位置到 m 结尾剩余的空间不足1个字节,write() 抛出异常ValueError,而 write_byte() 什么都不做。
方法的使用说明:介绍上面常用的方法
测试文本:test.txt,mmap对象m
-- MySQL dump 10.13 Distrib 5.6.19, for osx10.7 (x86_64) -- -- Host: localhost Database: test -- ------------------------------------------------------ -- Server version 5.6.19 /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!40101 SET NAMES utf8 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
①: m.close(),关闭对象
>>> import os,mmap >>> m=mmap.mmap(os.open('test.txt',os.O_RDWR),0) #创业内存映射对象, >>> m.read(10) #可以使用方法 '-- MySQL d' >>> m.close() #关闭对象 >>> m.read(10) #方法不可用 Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: mmap closed or invalid
②:m.find(str, start=0),从start的位置开始寻找第一次出现的str。
>>> m.find('SET',0) #从头开始查找第一次出现SET的字符串 197
③:m.read(n),返回一个从 m对象文件中读取的n个字节的字符串,将会把 m 对象的位置指针向后移动,后续读取会继续往下读。
>>> m.read(10) #读取10字节的字符串 '-- MySQL d' >>> m.read(10) #读取上面10字节后,再往后的10字节数据 'ump 10.13 '
④:m.read_byte(),返回一个1字节长的字符串,从 m 对应的文件中读1个字节
>>> m.read_byte() #读取第一个字节 '-' >>> m.read_byte() #读取第二个字节 '-' >>> m.read_byte() #读取第三个字节 ' '
⑤:m.readline():返回一个字符串,从 m 对应文件的当前位置到下一个' ',当调用 readline() 时文件位于 EOF,则返回空字符串
>>> m.readline() #读取一正行 '-- MySQL dump 10.13 Distrib 5.6.19, for osx10.7 (x86_64) ' >>> m.readline() #读取下一正行 '-- '
⑥:m.size():返回 m 对应文件的长度(不是 m 对象的长度len(m))
>>> m.size() #整个文件的大小 782
⑦:m.tell():返回 m 对应文件的当前光标位置
>>> m.tell() #当前光标的位置0 0 >>> m.read(10) #读取10个字节 '-- MySQL d' >>> m.tell() #当前光标位置10 10
⑧:m.seek(pos, how=0),改变 m 对应的文件的当前位置
>>> m.seek(10) #当前光标定位到10 >>> m.tell() #读取当前光标的位置 10 >>> m.read(10) #读取当前光标之后的10字节内容 'ump 10.13
⑨:m.move(dstoff, srcoff, n):等于 m[dstoff:dstoff+n] = m[srcoff:srcoff+n],把从 srcoff 开始的 n 个字节复制到从 dstoff 开始的n个字节
>>> m[101:108] #切片101到108的值 '-------' >>> m[1:8] #切片1到8的值 '- MySQL' >>> m.move(1,101,8) #从101开始到后面的8字节(108),替换从1开始到后面的8字节(8)效果:m[1:8]=m[101:108] >>> m[1:8] #被替换后 '-------'
⑩:m.write(str):把 str 写到 m 对应文件的当前光标位置(覆盖对应长度),如果从 m 对应文件的当前光标位置到 m 结尾剩余的空间不足len(str),则抛出 ValueError
>>> m.tell() #当前光标位置 0 >>> m.write('zhoujy') #写入str,要是写入的大小大于原本的文件,会报错。m.write_byte(byte)不会报错。
>>> m.tell() #写入后光标位置
6
>>> m.seek(0) #重置,光标从头开始
>>> m.read(10) #查看10个字节,确定是否被修改成功
'zhoujy---d'
⑪:m.flush():把 m 中从offset开始的n个字节刷到对应的文件中
注意:对于m的修改操作,可以当成一个列表进行切片操作,但是对于切片操作的修改需要改成同样长度的字符串,否则都会报错。如m中的10个字符串进行修改,必须改成10个字符的长度。
3,应用说明:
1):读文件,ACCESS_READ
①:读取整个文件
#!/usr/bin/python # -*- encoding: utf-8 -*- import mmap import contextlib f = open('test.txt', 'r') with contextlib.closing(mmap.mmap(f.fileno(), 0,access=mmap.ACCESS_READ)) as m:#readline需要循环才能读取整个文件 while True: line = m.readline().strip() print line #光标到最后位置(读完),就退出 if m.tell()==m.size(): break
效果:
~$ python untitled.py 1 ↵ -- ZHOUJY ---dump 10.13 Distrib 5.6.19, for osx10.7 (x86_64) -- -- Host: localhost Database: test -- ------------------------------------------------------ -- Server version 5.6.19 /*!40101 ZHOUJY SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */ZHOUJY; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!40101 SET NAMES utf8 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */ ZHOUJY; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
②:逐步读取指定字节数文件
#!/usr/bin/python # -*- encoding: utf-8 -*- import mmap import contextlib with open('test.txt', 'r') as f: with contextlib.closing(mmap.mmap(f.fileno(), 0,access=mmap.ACCESS_READ)) as m: print '读取10个字节的字符串 :', m.read(10) print '支持切片,对读取到的字符串进行切片操作:', m[2:10] print '读取之前光标后的10个字符串', m.read(10)
效果:
~$ python untitled.py 读取10个字节的字符串 : -- ZHOUJY 支持切片,对读取到的字符串进行切片操作: ZHOUJY 读取之前光标后的10个字符串 ---dump 1
2):查找文件,ACCESS_READ
①:从整个文件查找所有匹配的
#!/usr/bin/python # -*- encoding: utf-8 -*- import mmap import contextlib word = 'ZHOUJY' print '查找:', word f = open('test.txt', 'r') with contextlib.closing(mmap.mmap(f.fileno(), 0,access=mmap.ACCESS_READ)) as m: #也可以通过find(str,pos)来处理 while True: line = m.readline().strip() if line.find(word)>=0: print "结果:" print line elif m.tell()==m.size(): break else: pass
效果:
~$ python untitled.py 查找: ZHOUJY 结果: -- ZHOUJY ---dump 10.13 Distrib 5.6.19, for osx10.7 (x86_64) 结果: /*!40101 ZHOUJY SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */ZHOUJY; 结果: /*!40103 SET TIME_ZONE='+00:00' */ ZHOUJY;
②:从整个文件里查找,找到就退出(确认到底是否存在)
#!/usr/bin/python # -*- encoding: utf-8 -*- import mmap import contextlib word = 'ZHOUJY' print '查找:', word f = open('test.txt', 'r') with contextlib.closing(mmap.mmap(f.fileno(), 0,access=mmap.ACCESS_READ)) as m: #不需要循环,只要找到一个就可以了 loc = m.find(word) if loc >= 0: print loc print m[loc:loc+len(word)]
效果:
~$ python untitled.py 查找: ZHOUJY 194 ZHOUJY
③:通过正则查找,(找出40开头的数字)
#!/usr/bin/python # -*- encoding: utf-8 -*- import mmap import re import contextlib pattern = re.compile(r'(40d*)') with open('test.txt', 'r') as f: with contextlib.closing(mmap.mmap(f.fileno(), 0,access=mmap.ACCESS_READ)) as m: print pattern.findall(m)
效果:
~$ python untitled.py ['40101', '40101', '40101', '40101', '40103', '40103', '40014', '40014', '40101', '40111']
3):处理文本,只能等长处理(通过上面的查找方法,来替换查找出的内容),模式:ACCESS_WRITE、ACCESS_COPY