Python之mmap内存映射模块(大文本处理)说

来源:互联网 发布:阿里 人工智能平台 编辑:程序博客网 时间:2024/06/05 22:45

http://www.cnblogs.com/zhoujinyi/p/6062907.html

背景:

      通常在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 onlyos.O_WRONLY   以只写的方式打开 Write onlyos.O_RDWR     以读写的方式打开 Read and writeos.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_LIVEDos.O_TEMPORARY        与O_CREAT一起创建临时文件os.O_RANDOM         缓存优化,但不限制从磁盘中随机存取os.O_SEQUENTIAL   缓存优化,但不限制从磁盘中序列存取os.O_TEXT           以文本的模式打开文件(转换)
复制代码

length要映射文件部分的大小(以字节为单位),这个值为0,则映射整个文件,如果大小大于文件当前大小,则扩展这个文件。

flags:MAP_PRIVATE:这段内存映射只有本进程可用;mmap.MAP_SHARED:将内存映射和其他进程共享,所有映射了同一文件的进程,都能够看到其中一个所做的更改;
protmmap.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 对应文件的当前位置到下一个'\n',当调用 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 对应文件的当前位置到下一个'\n',当调用 readline() 时文件位于 EOF,则返回空字符串

>>> m.readline()             #读取一正行'-- MySQL dump 10.13  Distrib 5.6.19, for osx10.7 (x86_64)\n'>>> m.readline()             #读取下一正行'--\n'

⑥:m.size():返回 m 对应文件的长度(不是 m 对象的长度len(m))

>>> m.size()            #整个文件的大小782

⑦:m.tell():返回 m 对应文件的当前光标位置

>>> m.tell()        #当前光标的位置00>>> m.read(10)      #读取10个字节'-- MySQL d'>>> m.tell()        #当前光标位置1010

⑧: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 mmapimport contextlibf = 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 mmapimport contextlibwith 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 mmapimport contextlibword = 'ZHOUJY'print '查找:', wordf = open('test.txt', 'r')with contextlib.closing(mmap.mmap(f.fileno(), 0,access=mmap.ACCESS_READ)) as m:
#因为find只匹配最早出现的,所以需要一行行读取匹配
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 mmapimport contextlibword = 'ZHOUJY'print '查找:', wordf = 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查找: ZHOUJY194ZHOUJY

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

复制代码
#!/usr/bin/python# -*- encoding: utf-8 -*-import mmapimport reimport contextlib  pattern = re.compile(r'(40\d*)')  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

经过上面对mmap方法的介绍和使用说明,大致了解了mmap的特点。这里通过对比sed的方法,来看看到底处理大文件使用哪种方法更高效。 

①:替换文本中出现一次的内容。比如想把A库的备份文件(9G)还原到B库,需要把里面的USE `A`改成USE `B`。

1> sed处理:时间消耗近105s;磁盘IO几乎跑满;内存几乎没消耗、CPU消耗10~20%之间。

复制代码
1:替换文本中第一次出现的内容~$ date && sed -i '0,/USE `edcba`;/s//USE `ABCDE`;/' test.sql && date2016年 11月 16日 星期三 12:04:17 CST2016年 11月 16日 星期三 12:06:02 CST2:替换文本中指定行的内容~$ date && sed -i '24s/USE `ABCDE`;/USE `edcba`;/' test.sql && date2016年 11月 16日 星期三 12:09:05 CST2016年 11月 16日 星期三 12:10:50 CST
复制代码

IO消耗:

复制代码
Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %utilsda               1.00     7.00  772.00  105.00    87.22    92.06   418.65    27.90   31.35    2.21  245.56   1.14 100.00Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %utilsda               1.00     4.00  778.00  102.00    87.59    90.03   413.36    25.08   30.30    2.59  241.65   1.13  99.60Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %utilsda               2.00     5.00  771.00  101.00    87.48    88.04   412.22    29.80   30.24    2.34  243.21   1.14  99.60Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %utilsda               1.00    18.00  431.00  137.00    49.08   122.04   616.99    66.20   70.25    3.02  281.75   1.75  99.60Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %utilsda               0.00     1.00    1.00  248.00     0.00   177.04  1456.16   105.24  416.53   24.00  418.11   4.02 100.00
复制代码

2> python处理:时间消耗是毫秒级别的,几乎是秒级别完成,该情况比较特别:搜索的关键词在大文本里比较靠前的位置,这样处理上T的大文件也是非常快的,要是搜索的关键词靠后怎会怎么样呢?后面会说明。

复制代码
#!/usr/bin/python# -*- encoding: utf-8 -*-import mmapimport contextlibimport reword = 'USE `EDCBA`;'replace = 'USE `ABCDE`;'print '查找:', wordprint'替换:', replacef = open('test.sql', 'r+')with contextlib.closing(mmap.mmap(f.fileno(), 0,access=mmap.ACCESS_WRITE)) as m:    loc = m.find(word)    if loc >=0:        print loc        m[loc:loc + len(word)] = replace
复制代码

执行:

~$ date && python mmap_python.py && date2016年 11月 16日 星期三 12:14:19 CST查找: USE `EDCBA`;替换: USE `ABCDE`;9292016年 11月 16日 星期三 12:14:19 CST

②:替换文本中所有匹配的关键词。比如想把备份文件里的ENGINE=MYISAM改成ENGINE=InnoDB,看看性能如何。

1> sed处理:时间消耗110s;磁盘IO几乎跑满(读写IO高);内存几乎没消耗、CPU消耗10~30%之间。

~$ date && sed -i 's/ENGINE=InnoDB/ENGINE=MyISAM/g' test.sql && date2016年 11月 16日 星期三 12:19:30 CST2016年 11月 16日 星期三 12:21:20 CST 

和①中sed的执行效果差不多,其实对于处理一条还是多条记录,sed都是做同样工作量的事情,至于原因可以看了解sed的工作原理说明,个人理解大致意思就是:sed是1行1行读取(所以内存消耗很小),放入到自己设置的缓冲区里,替换完之后再写入(所以IO很高),处理速度受限于CPU和IO。

2> python处理:时间消耗60多秒,比sed少。因为不用重写所有内容,只需要替换指定的内容即可,并且是在内存中处理的,所以写IO的压力几乎没有。当关键词比较靠后,其读入的数据就比较大,文件需要从磁盘读入到内存,这时磁盘的读IO也很高,写IO还是没有。因为是虚拟内存映射文件,所以占用的物理内存不多,虽然通过TOP看到的内存使用率%mem很高,这里可以不用管,因为大部分都是在SHR列里的消耗,真正使用掉的内存可以通过RES-SHR来计算。关于top中SHR的意思,可以去看相关文章说明。

复制代码
#!/usr/bin/python# -*- encoding: utf-8 -*-import mmapimport contextlibword    = 'ENGINE=MyISAM'replace = 'ENGINE=InnoDB'print '查找:', wordprint'替换:', replacef = open('test.sql', 'r+')with contextlib.closing(mmap.mmap(f.fileno(), 0,access=mmap.ACCESS_WRITE)) as m:    while True:        loc = m.find(word)        if loc >=0:            print loc            m[loc:loc + len(word)] = replace            #要是access=mmap.ACCESS_COPY需要执行flush            #m.flush()        elif loc == -1:            break        else:            pass
复制代码

效果:

复制代码
~$ date && python mmap_python.py && date2016年 11月 16日 星期三 13:19:30 CST查找: ENGINE=MyISAM替换: ENGINE=InnoDB166358849381194125912630481129042616485216964859312650186926517961765181544657099301495718493592900115587495235479981518392016年 11月 16日 星期三 13:20:32 CST
复制代码

③:正则匹配修改,这个可以通过上面介绍的查找方法,做下修改即可,就不再做说明。

小结:

      对比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封装起来进行使用了,脚本信息:

复制代码
#!/usr/bin/python# -*- encoding: utf-8 -*-import mmapimport contextlibimport timefrom optparse import OptionParserdef calc_time(func):    def _deco(*args, **kwargs):        begin_time = time.time()        func(*args, **kwargs)        cost_time = time.time() - begin_time        print 'cost time: %s' % (cost_time)    return _deco@calc_timedef replace_keyword_all(filename,old_word,new_word):    if len(old_word) == len(new_word):        print "%s 替换成 %s " %(new_word,old_word)        with open(filename,'r+') as f:            with contextlib.closing(mmap.mmap(f.fileno(), 0,access=mmap.ACCESS_WRITE)) as m:                while True:                    loc = m.find(old_word)                    if loc >= 0:                        m[loc:loc+len(old_word)] = new_word                    elif loc == -1:                        break                    else:                        pass        f.close()    else:        print "替换的词要和被替换的词长度一致!"        exit()@calc_timedef replace_keyword_once(filename,old_word,new_word):    if len(old_word) == len(new_word):        print "%s 替换成 %s " %(new_word,old_word)        with open(filename,'r+') as f:            with contextlib.closing(mmap.mmap(f.fileno(), 0,access=mmap.ACCESS_WRITE)) as m:                loc = m.find(old_word)                if loc >= 0:                    m[loc:loc+len(old_word)] = new_word        f.close()    else:        print "替换的词要和被替换的词长度一致!"        exit()if __name__ == "__main__":    parser = OptionParser()    parser.add_option("-f", "--filename", help="Filename for search", dest="filename")    parser.add_option("-o", "--oldword", help="the ip to use", dest="old_word")    parser.add_option("-n", "--newword", help="the ip to use", dest="new_word")    (options, args) = parser.parse_args()    if not options.filename:        print 'params filename need to apply'        exit()    if not options.old_word:        print 'params oldword need to apply'        exit()    if not options.new_word:        print 'params newword need to apply'        exit()# 替换文本中第一次出现的内容(查到一个就处理退出,越靠前越快)#    replace_keyword_once(options.filename,options.old_word,options.new_word)# 替换文本中出现的内容(查找处理整个文本)    replace_keyword_all(options.filename,options.old_word,options.new_word)
复制代码

方法: 

复制代码
~$ python mmap_search.py -hUsage: mmap_search.py [options]Options:  -h, --help            show this help message and exit  -f FILENAME, --filename=FILENAME                        Filename for search  -o OLD_WORD, --oldword=OLD_WORD                        the ip to use  -n NEW_WORD, --newword=NEW_WORD                        the ip to use
复制代码

处理效果:(40G的文本)

复制代码
1)sed:~$ date && sed -i '0,/USE `EDCBA`;/s//USE `ABCDE`;/' test.sql && date2016年 11月 17日 星期四 11:15:33 CST2016年 11月 17日 星期四 11:21:47 CST2)mmap:替换文本中第一次出现的内容(查到一个就处理退出,越靠前越快)~$ python mmap_search.py --filename='test.sql' --oldword="USE \`EDCBA\`;" --newword="USE \`ABCDE\`;"USE `ABCDE`; 替换成 USE `EDCBA`; cost time: 0.0001289844512943)mmap:替换文本中出现的内容(查找处理整个文本)~$ date && sed -i '0,/USE `EDCBA`;/s//USE `ABCDE`;/' test.sql && date2016年 11月 17日 星期四 11:15:33 CST2016年 11月 17日 星期四 11:21:47 CST
复制代码

参考文档:

Memory-mapped file support

通过mmap库映射文件到内存用法

mmap模块与mmap对象

阅读全文
'); })();
0 0
原创粉丝点击
热门IT博客
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 矫诏 康熙遗诏 凤仪之诏物件如何获取 凤仪之诏探索 太平待诏归来日 内宴奉诏作 凤仪之诏探索怎么解锁物品 上海诏业网络科技有限公司 奉天承运皇帝诏曰 溥仪看到康熙传位遗诏 牡丹仙子之皇帝诏曰 韩译中 泰译中 语音中译日 中试 中试线 提示语在前丶中丶后造句 现实中我沉默不语 名著中的性描写语段 语的含义名字中 一语中的读音 一语中的的意思 提示语在前中后的句子 提示语在中的句子 一语中的是什么意思 局中局之新娘归来 晨中语 网络语中二是什么意思 朋友的爸爸3中语观 善良的妈妈5中语观看 善良的妈妈3中语观看 中语 朋友的女朋友中语版 中诺电话机 中诺 中诺电话机设置 中诺电话机使用说明书 现实中我唯唯诺诺不敢说话 中诺电话机说明书 北京中诺口腔医院 中诺电话机音量设置 诺安中小盘