日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

Python中mmap模塊處理大文本的操作方法_python

作者:程序猿-張益達 ? 更新時間: 2023-04-10 編程語言

如果現在有一個需求,我們需要處理一個20G的大文件,我們會怎么處理呢?思考下,我們需要怎么實現這個功能。

我們可能會這么實現:

def get_datas():
    source_text_path = "路徑"
    with open(source_text_path, 'rb') as f:
        data = f.readlines()
    yield data
if __name__ == '__main__':
    for e in get_datas():
        deal_data(e)  # 處理數據

這樣雖然能實現,但是我們處理的時候需要消耗的資源和性能不是很友好,所以我們要優化,也就是使用mmap模塊。

mmap是一種虛擬內存映射文件的方法,即將一個文件或者其它對象映射到進程的地址空間,實現文件磁盤地址和進程虛擬地址空間中一段虛擬地址的一一映射關系。它省掉了內核態和用戶態頁copy這個動作(兩態間copy),直接將用戶態的虛擬地址與內核態空間進行映射,進程直接讀取內核空間,速度提高了,內存占用也少了。

簡單點來說,mmap函數實現的是內存共享。內存共享是兩個不同的進程共享內存的意思:同一塊物理內存被映射到兩個進程的各自的進程地址空間。這個物理內存已經被規定了大?。ù笮∫欢ㄒ葘嶋H寫入的東東大)以及名稱。當需要寫入時,找到內存名稱,然后寫入內存,等需要讀取時候, 首先要知道你要讀取多大(因為物理內存比你要讀取的東西大,全部讀取的話會讀到一些“空”的東西),然后尋找對應名稱的物理塊,然后讀取。

mmap 介紹

Windows

mmap.mmap(fileno, length, tagname=None, access=ACCESS_DEFAULT[, offset])

參數說明:

  • fileno:文件描述符,可以是file對象的fileno()方法,或者來自os.open(),在調用mmap()之前打開文件,不再需要文件時要關閉。

  • length:要映射文件部分的大小(以字節為單位),這個值為0,則映射整個文件,如果大小大于文件當前大小,則擴展這個文件。

  • tagname:為映射提供標簽名稱的字符串,Windows 允許你對同一文件擁有許多不同的映射。如果指定現有標簽的名稱,則會打開該標簽,否則將創建該名稱的新標簽。如果省略此參數或設置為None ,則創建的映射不帶名稱。避免使用tag參數將有助于使代碼在Unix和Windows之間可移植。

  • access:文件權限

  • ACCESS_READ:讀訪問;

  • ACCESS_WRITE:寫訪問,默認;

  • ACCESS_COPY:拷貝訪問,不會把更改寫入到文件,使用flush把更改寫到文件。

offset:非負整數偏移量,默認從0開始。

Unix

mmap.mmap(fileno, length, flags=MAP_SHARED, prot=PROT_WRITE|PROT_READ, access=ACCESS_DEFAULT[, offset])

參數說明:

  • flags:映射的性質,默認MAP_SHARED。
  • MAP_PRIVATE 會創建私有的寫入時拷貝映射,因此對mmap對象內容的修改將為該進程所私有;
  • MAP_SHARED 會創建與其他映射同一文件區域的進程所共享的映射。
  • prot:它將給出所需的內存保護方式;最有用的兩個值是 PROT_READ和 PROT_WRITE,分別指明頁面為可讀或可寫。 prot 默認為PROT_READ | PROT_WRITE。
  • access:注意的是可以指定 access 作為替代 flags 和 prot 的可選關鍵字形參。 同時指定 flags,prot 和 access 將導致錯誤。

fileno文件描述符有如下

  • os.O_RDONLY:以只讀的方式打開Readonly
  • 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:以文本的模式打開文件(轉換)

支持的方法

  • close(): 關閉 mmap。 后續調用該對象的其他方法將導致引發 ValueError 異常。 此方法將不會關閉打開的文件。
  • closed: 如果文件已關閉則返回 True。
  • find(str, start, end): 從 start 下標開始,在 m中從左往右尋找子串 str最早出現的下標;沒有找到則返回-1。
  • flush([offset, n]):將對文件的內存副本的修改刷新至磁盤。 如果不使用此調用則無法保證在對象被銷毀前將修改寫回存儲。 如果指定了 offset和 size,則只將對指定范圍內字節的修改刷新至磁盤;在其他情況下,映射的全部范圍都會被刷新。
  • windows: 返回的非零值表示成功;否則返回0。 零表示失敗。
  • unix: 返回零值以表示成功。 當調用失敗時將引發異常。
  • move(dest, src, count): 將從偏移量 src開始的 count個字節拷貝到目標索引號 dest。 如果 mmap 創建時設置了 ACCESS_READ,則調用 move將引發異常。
  • read([n]): 返回一個字節,其中包含從當前文件位置開始的至多 n 個字節。 如果參數省略,為 None 或負數,則返回從當前文件位置開始直至映射結尾的所有字節。 文件位置會被更新為返回字節數據之后的位置
  • read_byte():返回一個1字節長的字符串,從 m 對應的文件中讀1個字節,要是已經到了EOF還調用 read_byte(),則拋出異常 ValueError。
  • readline():返回一個字符串,從 m 對應文件的當前位置到下一個’\n’,當調用 readline() 時文件位于 EOF,則返回空字符串。
  • resize(newsize):如果存在的話, 改變映射以及下層文件的大小。 如果 mmap 創建時設置了 ACCESS_READ或 ACCESS_COPY,則改變映射大小將引發異常。
  • rfind(sub[, start[, end]]):返回子序列 sub在對象內被找到的最大索引號,使得 sub 被包含在 [start, end] 范圍中。 可選參數 start和 end 會被解讀為切片表示法。 如果未找到則返回 -1。
  • seek(pos[, whence]):設置文件的當前位置。 whence 參數為可選項并且默認為 os.SEEK_SET 或 0 (絕對文件定位);其他值還有 os.SEEK_CUR 或 1 (相對當前位置查找) 和 os.SEEK_END 或 2 (相對文件末尾查找)。
  • size():返回文件的長度,該數值可以大于內存映射區域的大小。
  • tell():返回文件指針的當前位置。
  • write(str):將str寫入文件指針當前位置的內存并返回寫入的字節總數 (一定不小于 len(str),因為如果寫入失敗,將會引發錯誤)。 在字節數據被寫入后文件位置將會更新。 如果 mmap 創建時設置了 ACCESS_READ,則向其寫入將引發 異常
  • write_byte(byte):將整數值 byte 寫入文件指針當前位置的內存;文件位置前進 1。 如果 mmap 創建時設置了 ACCESS_READ,則向其寫入將引發異常。

對于EOF的處理,write() 和 read_byte() 拋出異常 ValueError,而 write_byte() 和 read() 什么都不做。

使用mmap讀取大文件

from mmap import mmap
def read_data(file_path):
    with open(file_path, "r+") as f:
        m = mmap(f.fileno(), 0)
        g_index = 0
        for index, char in enumerate(m):
            if char == b"\n":
                yield m[g_index:index + 1].decode()
                g_index = index + 1
if __name__ == "__main__":
    file_path = ""
    for content in read_data(file_path):
        print(content)

什么時候用mmap?

用mmap來讀取超大文件,不是mmap的主要應用場景,Python官方文件也沒有提到這一點。如果僅僅是讀取超大文件,使用文件對象的read(N),來得更快更好更簡單。

關于標準庫中的mmap模塊?,F有一個需求,要對超大文件(接近40G)進行讀寫,notepad++等工具直接拒絕打開此文件。用 r+ 模式打開文件,可以隨意讀寫,但是要特別小心。readline()是否能夠使用,要看這個文件每行都多長,如果沒有換行,就不能用,就算知道每行的大小,也要帶個參數N來控制最大讀取數量。readlines()是肯定不能用的,就算帶參數,也可能直接卡死!read(N)沒問題,主要控制是N的大小。

總之,傳統讀寫文件的方式可以用,但是不夠方便。速度也是個問題,傳統的緩存IO方式,涉及到OS內核態的內存和進程虛擬空間內存的內容交換,對于超大文件而言,這種交換會浪費大量的CPU時間和內存。mmap是另一個方式!它省掉了內核態和用戶態頁拷貝這個動作(兩態間copy),直接將用戶態的虛擬地址與內核態空間進行映射,進程直接讀取內核空間,速度提高了,內存占用也少了。

總結來說,常規文件操作為了提高讀寫效率和保護磁盤,使用了頁緩存機制。這樣造成讀文件時需要先將文件頁從磁盤拷貝到頁緩存中,由于頁緩存處在內核空間,不能被用戶進程直接尋址,所以還需要將頁緩存中數據頁再次拷貝到內存對應的用戶空間中。這樣,通過了兩次數據拷貝過程,才能完成進程對文件內容的獲取任務。寫操作也是一樣,待寫入的buffer在內核空間不能直接訪問,必須要先拷貝至內核空間對應的主存,再寫回磁盤中(延遲寫回),也是需要兩次數據拷貝。

而使用mmap操作文件中,創建新的虛擬內存區域和建立文件磁盤地址和虛擬內存區域映射這兩步,沒有任何文件拷貝操作。而之后訪問數據時發現內存中并無數據而發起的缺頁異常過程,可以通過已經建立好的映射關系,只使用一次數據拷貝,就從磁盤中將數據傳入內存的用戶空間中,供進程使用。

總而言之,常規文件操作需要從磁盤到頁緩存再到用戶主存的兩次數據拷貝。而mmap操控文件,只需要從磁盤到用戶主存的一次數據拷貝過程。說白了,mmap的關鍵點是實現了用戶空間和內核空間的數據直接交互而省去了空間不同數據不通的繁瑣過程。因此在某些場景下,mmap效率更高。

從python官網上看mmap的介紹,生成的mmap對象,就像一個bytearray對象,可以直接用index的方式讀寫,可以切片。同時,mmap對象還有一組類似文件操作的接口,read,readline,flush等等。即mmap對象兼具bytearray和file對象的功能。不過還是要注意,對于超大文件的讀(先不考慮寫的問題吧),從磁盤到內核,依然會占用內存,因此絕對不能一口氣全部讀出來。read(N)是必須的,mmap的使用只是可能會提高效率。(如果頻繁的創建和關閉mmap映射,這種創建是為了指向超大文件的不同位置,反而效率更低。一般情況下的read(N)實現,不需要使用mmap。)

mmap的另一個應用場景,是進程間的內存共享。多個進程將同一個文件map到同一段內核地址上,即實現了相互之間的共同訪問。

總結:使用mmap的時機

  • 將一個普通文件映射到內存中,通常在需要對文件進行頻繁讀寫時使用,這樣用內存映射讀寫取代I/O緩存讀寫,以獲得較高的性能;
  • 將特殊文件進行匿名內存映射,可以為關聯進程提供共享內存空間;
  • 為無關聯的進程提供共享內存空間,一般也是將一個普通文件映射到內存中。

原文鏈接:https://blog.csdn.net/weixin_41951954/article/details/128837722

欄目分類
最近更新