1. 程式人生 > 實用技巧 >Python之mmap記憶體對映模組(大文字處理)

Python之mmap記憶體對映模組(大文字處理)

背景:

通常在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 對應檔案的當前位置到下一個'\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()        #當前游標的位置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'(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 && date
2016年 11月 16日 星期三 12:04:17 CST
2016年 11月 16日 星期三 12:06:02 CST

2:替換文字中指定行的內容
~$ date && sed -i '24s/USE `ABCDE`;/USE `edcba`;/' test.sql && date
2016年 11月 16日 星期三 12:09:05 CST
2016年 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  %util
sda               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.00

Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda               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.60

Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda               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.60

Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda               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.60

Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda               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 mmap
import contextlib
import re


word = 'USE `EDCBA`;'
replace = 'USE `ABCDE`;'
print '查詢:', word
print'替換:', replace

f = 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 && date
2016年 11月 16日 星期三 12:14:19 CST
查詢: USE `EDCBA`;
替換: USE `ABCDE`;
929
2016年 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 && date
2016年 11月 16日 星期三 12:19:30 CST
2016年 11月 16日 星期三 12:21:20 CST

和①中sed的執行效果差不多,其實對於處理一條還是多條記錄,sed都是做同樣工作量的事情,至於原因可以看瞭解sed的工作原理說明,個人理解大致意思就是:sed是1行1行讀取(所以記憶體消耗很小),放入到自己設定的緩衝區裡,替換完之後再寫入(所以IO很高),處理速度受限於CPU和IO。

2> python處理:時間消耗20多秒,比sed少。因為不用重寫所有內容,只需要替換指定的內容即可,並且是在記憶體中處理的,所以寫IO的壓力幾乎沒有。當關鍵詞比較靠後,其讀入的資料就比較大,檔案需要從磁碟讀入到記憶體,這時磁碟的讀IO也很高,寫IO還是沒有。因為是虛擬記憶體對映檔案,所以佔用的實體記憶體不多,雖然通過TOP看到的記憶體使用率%mem很高,這裡可以不用管,因為大部分都是在SHR列裡的消耗,真正使用掉的記憶體可以通過RES-SHR來計算。關於top中SHR的意思,可以去看相關文章說明。

#!/usr/bin/python
# -*- encoding: utf-8 -*-

import mmap
import contextlib

word    = 'ENGINE=MyISAM'
replace = 'ENGINE=InnoDB'
print '查詢:', word
print'替換:', replace

loc = 0 f = open('test.sql', 'r+') with contextlib.closing(mmap.mmap(f.fileno(), 0,access=mmap.ACCESS_WRITE)) as m: while True: loc = m.find(word,loc) 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 && date
2016年 11月 16日 星期三 13:19:30 CST
查詢: ENGINE=MyISAM
替換: ENGINE=InnoDB
1663
5884938
11941259
12630481
12904261
64852169
64859312
65018692
65179617
65181544
65709930
149571849
3592900115
5874952354
7998151839
2016年 11月 16日 星期三 13:19:55 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 mmap
import contextlib
import time
from optparse import OptionParser


def 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_time
def replace_keyword_all(filename,old_word,new_word):
    if len(old_word) == len(new_word):
        loc = 0
        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,loc)
                    if loc >= 0:
                        m[loc:loc+len(old_word)] = new_word
                    elif loc == -1:
                        break
                    else:
                        pass
        f.close()
    else:
        print "替換的詞要和被替換的詞長度一致!"
        exit()


@calc_time
def 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 -h
Usage: 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 && date
2016年 11月 17日 星期四 11:15:33 CST
2016年 11月 17日 星期四 11:21:47 CST

2)mmap:替換文字中第一次出現的內容(使用replace_keyword_once方法,查到一個就處理退出,越靠前越快)
~$ python mmap_search.py --filename='test.sql' --oldword="USE \`EDCBA\`;" --newword="USE \`ABCDE\`;"
USE `ABCDE`; 替換成 USE `EDCBA`; 
cost time: 0.000128984451294

3)sed:替換文字中出現的內容(查詢處理整個文字)
~$ date && sed -i 's/ENGINE=InnoDB/ENGINE=MyISAM/g' test.sql && date
2016年 11月 17日 星期四 10:04:49 CST
2016年 11月 17日 星期四 10:11:34 CST

4)mmap:替換文字中出現的內容(使用replace_keyword_all方法,查詢處理整個文字)
~$ python mmap_search.py --filename="test.sql" --oldword="ENGINE=MyISAM" --newword="ENGINE=InnoDB"
ENGINE=InnoDB 替換成 ENGINE=MyISAM 
cost time: 198.471223116

結論:修改大文字檔案,通過sed處理,不管被修改的詞在哪個位置都需要重寫整個檔案;而mmap修改文字,被修改的詞越靠前效能越好,不需要重寫整個文字,只要替換被修改詞語的長度即可。