5.10 內(nèi)存映射的二進(jìn)制文件

2018-02-24 15:26 更新

問(wèn)題

你想內(nèi)存映射一個(gè)二進(jìn)制文件到一個(gè)可變字節(jié)數(shù)組中,目的可能是為了隨機(jī)訪問(wèn)它的內(nèi)容或者是原地做些修改。

解決方案

使用 mmap 模塊來(lái)內(nèi)存映射文件。下面是一個(gè)工具函數(shù),向你演示了如何打開(kāi)一個(gè)文件并以一種便捷方式內(nèi)存映射這個(gè)文件。

import os
import mmap

def memory_map(filename, access=mmap.ACCESS_WRITE):
    size = os.path.getsize(filename)
    fd = os.open(filename, os.O_RDWR)
    return mmap.mmap(fd, size, access=access)

為了使用這個(gè)函數(shù),你需要有一個(gè)已創(chuàng)建并且內(nèi)容不為空的文件。下面是一個(gè)例子,教你怎樣初始創(chuàng)建一個(gè)文件并將其內(nèi)容擴(kuò)充到指定大?。?/p>

>>> size = 1000000
>>> with open('data', 'wb') as f:
...     f.seek(size-1)
...     f.write(b'\x00')
...
>>>

下面是一個(gè)利用 memory_map() 函數(shù)類(lèi)內(nèi)存映射文件內(nèi)容的例子:

>>> m = memory_map('data')
>>> len(m)
1000000
>>> m[0:10]
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
>>> m[0]
0
>>> # Reassign a slice
>>> m[0:11] = b'Hello World'
>>> m.close()

>>> # Verify that changes were made
>>> with open('data', 'rb') as f:
... print(f.read(11))
...
b'Hello World'
>>>

mmap() 返回的 mmap 對(duì)象同樣也可以作為一個(gè)上下文管理器來(lái)使用,這時(shí)候底層的文件會(huì)被自動(dòng)關(guān)閉。比如:

>>> with memory_map('data') as m:
...     print(len(m))
...     print(m[0:10])
...
1000000
b'Hello World'
>>> m.closed
True
>>>

默認(rèn)情況下,memeory_map() 函數(shù)打開(kāi)的文件同時(shí)支持讀和寫(xiě)操作。任何的修改內(nèi)容都會(huì)復(fù)制回原來(lái)的文件中。如果需要只讀的訪問(wèn)模式,可以給參數(shù) access 賦值為 mmap.ACCESS_READ 。比如:

m = memory_map(filename, mmap.ACCESS_READ)

如果你想在本地修改數(shù)據(jù),但是又不想將修改寫(xiě)回到原始文件中,可以使用 mmap.ACCESS_COPY

m = memory_map(filename, mmap.ACCESS_COPY)

討論

為了隨機(jī)訪問(wèn)文件的內(nèi)容,使用 mmap 將文件映射到內(nèi)存中是一個(gè)高效和優(yōu)雅的方法。例如,你無(wú)需打開(kāi)一個(gè)文件并執(zhí)行大量的 seek()read() ,write() 調(diào)用,只需要簡(jiǎn)單的映射文件并使用切片操作訪問(wèn)數(shù)據(jù)即可。

一般來(lái)講,mmap() 所暴露的內(nèi)存看上去就是一個(gè)二進(jìn)制數(shù)組對(duì)象。但是,你可以使用一個(gè)內(nèi)存視圖來(lái)解析其中的數(shù)據(jù)。比如:

>>> m = memory_map('data')
>>> # Memoryview of unsigned integers
>>> v = memoryview(m).cast('I')
>>> v[0] = 7
>>> m[0:4]
b'\x07\x00\x00\x00'
>>> m[0:4] = b'\x07\x01\x00\x00'
>>> v[0]
263
>>>

需要強(qiáng)調(diào)的一點(diǎn)是,內(nèi)存映射一個(gè)文件并不會(huì)導(dǎo)致整個(gè)文件被讀取到內(nèi)存中。也就是說(shuō),文件并沒(méi)有被復(fù)制到內(nèi)存緩存或數(shù)組中。相反,操作系統(tǒng)僅僅為文件內(nèi)容保留了一段虛擬內(nèi)存。當(dāng)你訪問(wèn)文件的不同區(qū)域時(shí),這些區(qū)域的內(nèi)容才根據(jù)需要被讀取并映射到內(nèi)存區(qū)域中。而那些從沒(méi)被訪問(wèn)到的部分還是留在磁盤(pán)上。所有這些過(guò)程是透明的,在幕后完成!

如果多個(gè)Python解釋器內(nèi)存映射同一個(gè)文件,得到的 mmap 對(duì)象能夠被用來(lái)在解釋器直接交換數(shù)據(jù)。也就是說(shuō),所有解釋器都能同時(shí)讀寫(xiě)數(shù)據(jù),并且其中一個(gè)解釋器所做的修改會(huì)自動(dòng)呈現(xiàn)在其他解釋器中。很明顯,這里需要考慮同步的問(wèn)題。但是這種方法有時(shí)候可以用來(lái)在管道或套接字間傳遞數(shù)據(jù)。

這一小節(jié)中函數(shù)盡量寫(xiě)得很通用,同時(shí)適用于Unix和Windows平臺(tái)。要注意的是使用 mmap() 函數(shù)時(shí)會(huì)在底層有一些平臺(tái)的差異性。另外,還有一些選項(xiàng)可以用來(lái)創(chuàng)建匿名的內(nèi)存映射區(qū)域。如果你對(duì)這個(gè)感興趣,確保你仔細(xì)研讀了Python文檔中這方面的內(nèi)容 。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)