通常在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對象
~~~~~~~~~~~~~~~
萬物之中,希望至美