想要高效讀寫文件嗎?Python的Mmap()函數(shù)或許可以解決你的問題
文本文件的追加和截?cái)?/h2>文件追加的概念和應(yīng)用
在某些場(chǎng)景下,我們需要對(duì)已有的文件進(jìn)行修改,常見的方式是打開文件后將新的內(nèi)容寫入,這會(huì)覆蓋掉原來的內(nèi)容。但如果想保留原文件的內(nèi)容并在其末尾添加新的內(nèi)容,就需要用到文件追加操作。文件追加是指向一個(gè)已存在的文件末尾添加新的內(nèi)容,并且不影響原先的內(nèi)容。
使用 a 模式打開文本文件進(jìn)行追加操作
Python中使用 open() 函數(shù)打開文件時(shí),可以通過設(shè)置文件模式參數(shù)來指定文件的操作方式。其中,a 模式表示以追加的方式打開文件,即將新的內(nèi)容添加到文件末尾。
with open('file.txt', 'a') as f:
f.write('Hello, world!\n')
在上述代碼中,我們打開了一個(gè)名為 file.txt 的文件,并使用 a 模式將字符串 'Hello, world!\n' 寫入文件末尾。需要注意的是,在使用 a 模式時(shí),如果文件不存在,則會(huì)自動(dòng)創(chuàng)建新文件。
truncate 方法實(shí)現(xiàn)文本文件截?cái)?/h4>
除了在文件末尾追加新內(nèi)容之外,我們有時(shí)還需要對(duì)文件進(jìn)行截?cái)嗖僮鳎粗槐A粑募皫仔谢蚯皫讉€(gè)字符,而舍棄文件中后面的內(nèi)容。Python中提供了 truncate() 方法來實(shí)現(xiàn)這一功能。truncate() 方法可以指定文件的長(zhǎng)度(以字節(jié)為單位),使文件中多余的部分被刪除掉,從而實(shí)現(xiàn)文件截?cái)唷?/p>
with open('file.txt', 'r+') as f:
f.seek(0) # 將文件指針移到文件開頭位置
f.truncate(10) # 截?cái)辔募?,保留?0個(gè)字符
在上述代碼中,我們首先使用 r+ 模式打開文件,并將文件指針移到文件開頭位置,然后使用 truncate() 方法截?cái)辔募?,并指定保留文件?0個(gè)字符。需要注意的是,在使用 truncate() 方法時(shí),必須以讀寫模式打開文件,否則會(huì)拋出異常。
示例代碼
下面是一個(gè)完整的示例代碼,演示了如何使用 a 模式打開文件追加內(nèi)容,并使用 truncate() 方法截?cái)辔募?/p>
def append_and_truncate():
with open('file.txt', 'a') as f:
f.write('Hello, world!\n')
with open('file.txt', 'r+') as f:
f.seek(0)
f.truncate(10)
with open('file.txt', 'r') as f:
print(f.read())
在上述代碼中,我們首先使用 a 模式打開文件 file.txt 并寫入一行文本,然后再以 r+ 模式打開同一文件進(jìn)行截?cái)嗖僮?,保留?0個(gè)字符。最后,我們以只讀模式打開文件并打印其內(nèi)容,結(jié)果應(yīng)該如下所示:
Hello, worl
with 語句原理
with 語句的作用和優(yōu)勢(shì)
with語句是Python提供的一種簡(jiǎn)化文件操作的語法結(jié)構(gòu),其作用是在文件使用完后自動(dòng)關(guān)閉文件,避免了手動(dòng)關(guān)閉文件時(shí)可能出現(xiàn)的錯(cuò)誤。除了文件操作之外,with語句還可以用于其他資源的管理,例如網(wǎng)絡(luò)連接、數(shù)據(jù)庫連接等。
with open('file.txt', 'r') as f:
data = f.read()
在上述代碼中,我們使用 with 語句打開文件 file.txt 并讀取其中的內(nèi)容,這樣即使在處理文件過程中出現(xiàn)異常,Python也會(huì)自動(dòng)關(guān)閉文件,避免了文件資源泄露的問題??梢钥吹?,使用 with 語句可以讓代碼更加簡(jiǎn)潔和優(yōu)雅。
with 語句原理及其底層實(shí)現(xiàn)
with語句的實(shí)現(xiàn)原理是基于上下文管理器(context manager)的概念。上下文管理器是一個(gè)對(duì)象,它定義了進(jìn)入和退出某個(gè)上下文時(shí)要執(zhí)行的操作。使用 with 語句時(shí),必須將一個(gè)支持上下文管理器協(xié)議的對(duì)象傳遞給它,然后 with 語句會(huì)在進(jìn)入和退出上下文時(shí)自動(dòng)調(diào)用該對(duì)象的 enter() 和 exit() 方法。
class File:
def __init__(self, filename):
self.filename = filename
def __enter__(self):
print('Enter')
self.file = open(self.filename, 'r')
return self.file
def __exit__(self, exc_type, exc_value, traceback):
print('Exit')
self.file.close()
with File('file.txt') as f:
data = f.read()
在上述代碼中,我們定義了一個(gè)名為 File 的上下文管理器,并實(shí)現(xiàn)了其 enter() 和 exit() 方法。在 with 語句中使用 File 對(duì)象時(shí),Python會(huì)自動(dòng)調(diào)用其 enter() 方法打開文件,并在代碼塊執(zhí)行完畢后調(diào)用 exit() 方法關(guān)閉文件。
示例代碼
下面是一個(gè)完整的示例代碼,演示了如何使用 with 語句打開文件并讀取其中的內(nèi)容。
class File:
def __init__(self, filename):
self.filename = filename
def __enter__(self):
self.file = open(self.filename, 'r')
return self.file
def __exit__(self, exc_type, exc_value, traceback):
self.file.close()
def read_file():
with File('file.txt') as f:
data = f.read()
print(data)
if __name__ == '__main__':
read_file()
在上述代碼中,我們定義了一個(gè)名為 File 的上下文管理器,并在 read_file() 函數(shù)中使用 with 語句打開文件 file.txt 并讀取其中的內(nèi)容,然后自動(dòng)關(guān)閉文件。需要注意的是,在 with 語句中打開文件時(shí),必須指定文件模式參數(shù),并且不能使用 a 模式進(jìn)行追加操作。
seek 和 tell 方法
seek 和 tell 方法的作用和區(qū)別
在Python中,文件對(duì)象提供了兩個(gè)基本方法來控制文件指針的位置:seek() 和 tell()。其中,seek() 方法用于將文件指針移到文件的任意位置,而 tell() 方法則返回當(dāng)前文件指針的位置。
with open('file.txt', 'r') as f:
data = f.read(10) # 讀取前10個(gè)字符
pos = f.tell() # 獲取當(dāng)前文件指針位置
f.seek(0) # 將文件指針移到文件開頭位置
data2 = f.read(10) # 重新讀取前10個(gè)字符
print(pos)
print(data2)
在上述代碼中,我們使用 with 語句打開文件 file.txt 并讀取其中的前10個(gè)字符,然后獲取當(dāng)前文件指針的位置,并將文件指針移到文件開頭位置,最后重新讀取前10個(gè)字符。需要注意的是,在使用 seek() 方法時(shí),必須以二進(jìn)制模式打開文件。
文件指針和偏移量的概念和使用方法
文件指針是一個(gè)表示當(dāng)前讀寫位置的指針,它指向文件中下一個(gè)要讀取或?qū)懭氲淖止?jié)的位置。在Python中,文件指針的位置可以通過 tell() 方法獲取,并且可以使用 seek() 方法將其設(shè)置為任意位置。seek() 方法接受一個(gè)整數(shù)參數(shù),代表相對(duì)于文件開頭的偏移量(以字節(jié)為單位),并可指定偏移量的起始位置(0表示文件開頭,1表示當(dāng)前位置,2表示文件末尾)。
with open('file.txt', 'r') as f:
f.seek(5) # 將文件指針移到第6個(gè)字符處
data = f.read() # 從第6個(gè)字符開始讀取文件內(nèi)容
在上述代碼中,我們使用 seek() 方法將文件指針移到第6個(gè)字符處,然后讀取從該位置開始的文件內(nèi)容。
實(shí)現(xiàn)隨機(jī)訪問和修改文件內(nèi)容
由于可以通過 seek() 方法將文件指針移到文件的任意位置,因此可以實(shí)現(xiàn)隨機(jī)訪問文件內(nèi)容。例如,我們可以通過 seek() 方法將文件指針移到某一行的開頭位置,然后讀取該行的內(nèi)容。類似地,我們也可以使用 seek() 和 write() 方法來修改文件的特定位置。
with open('file.txt', 'r+') as f:
f.seek(5) # 將文件指針移到第6個(gè)字符處
f.write('WORLD') # 將字符 WORLD 插入到文件中
f.seek(0) # 將文件指針移到文件開頭位置
data = f.read() # 重新讀取文件內(nèi)容
print(data)
在上述代碼中,我們使用 r+ 模式打開文件 file.txt,并將文件指針移到第6個(gè)字符處,然后使用 write() 方法向文件中插入字符串 'WORLD'。最后,我們?cè)俅螌⑽募羔樢频轿募_頭位置并讀取文件的全部?jī)?nèi)容,輸出結(jié)果應(yīng)該為:
HelloWORLD, how are you?
示例代碼
下面是一個(gè)完整的示例代碼,演示了如何使用 seek() 和 tell() 方法實(shí)現(xiàn)隨機(jī)訪問和修改文件內(nèi)容。
def random_access():
with open('file.txt', 'r+') as f:
f.seek(5)
f.write('WORLD')
f.seek(0)
data = f.read()
print(data)
if __name__ == '__main__':
random_access()
在上述代碼中,我們首先使用 r+ 模式打開文件 file.txt 并將文件指針移到第6個(gè)字符處,然后使用 write() 方法插入字符串 'WORLD'。最后,我們重新將文件指針移到文件開頭位置并讀取文件的全部?jī)?nèi)容,輸出結(jié)果應(yīng)該為:
HelloWORLD, how are you?
內(nèi)存映射文件(mmap)
mmap 的作用和優(yōu)勢(shì)
Python中提供了一種特殊的文件操作方式,稱為內(nèi)存映射文件(mmap)。內(nèi)存映射文件是一種將文件內(nèi)容映射到內(nèi)存中的技術(shù),它允許我們通過內(nèi)存來讀寫文件內(nèi)容,從而避免了頻繁訪問磁盤的開銷。同時(shí),內(nèi)存映射文件還可以讓我們像處理數(shù)組一樣高效地對(duì)文件進(jìn)行隨機(jī)訪問和修改。在處理大型二進(jìn)制文件時(shí),內(nèi)存映射文件非常有用。
mmap 原理及其底層實(shí)現(xiàn)
在Python中,使用 mmap() 函數(shù)可以將一個(gè)文件對(duì)象映射到內(nèi)存中,從而生成一個(gè)內(nèi)存映射文件對(duì)象。內(nèi)存映射文件對(duì)象具有文件對(duì)象的所有方法,例如 read()、write()、seek() 等,并且也可以像操作數(shù)組一樣進(jìn)行隨機(jī)訪問和修改。
import mmap
with open('file.bin', 'r+b') as f:
mm = mmap.mmap(f.fileno(), 0)
# 讀取前10個(gè)字節(jié)
data1 = mm[:10]
print(data1)
# 修改前5個(gè)字節(jié)
mm[:5] = b'Hello'
# 查找字符串
pos = mm.find(b'world')
print(pos)
# 替換字符串
mm[pos:pos+5] = b'WORLD'
# 關(guān)閉內(nèi)存映射文件
mm.close()
在上述代碼中,我們使用 mmap() 函數(shù)將文件 file.bin 映射到內(nèi)存中,并獲取了一個(gè)內(nèi)存映射文件對(duì)象 mm。然后,我們可以像處理數(shù)組一樣對(duì)內(nèi)存映射文件進(jìn)行讀寫操作。例如,我們可以使用切片符號(hào) [:] 來讀取文件的前10個(gè)字節(jié),使用 find() 方法查找特定字符串的位置,并使用切片符號(hào)來替換字符串中的部分內(nèi)容。最后,我們調(diào)用 close() 方法關(guān)閉內(nèi)存映射文件。
注意事項(xiàng)
在使用 mmap() 函數(shù)時(shí),需要注意以下幾點(diǎn):
- 內(nèi)存映射文件只能用于二進(jìn)制文件的處理,不支持文本模式。
- 內(nèi)存映射文件是通過共享內(nèi)存實(shí)現(xiàn)的,在修改文件內(nèi)容時(shí)需要注意并發(fā)訪問問題,否則可能導(dǎo)致數(shù)據(jù)損壞或進(jìn)程掛起。
- 在某些操作系統(tǒng)上,如果文件長(zhǎng)度超過了可用的虛擬內(nèi)存大小,則無法創(chuàng)建內(nèi)存映射文件對(duì)象。
由于 Python 的 mmap() 函數(shù)依賴于底層操作系統(tǒng)的 mmap() 系統(tǒng)調(diào)用,因此其行為和性能可能在不同的操作系統(tǒng)上有所不同。在編寫使用 mmap() 函數(shù)的代碼時(shí),通常需要對(duì)其進(jìn)行測(cè)試和優(yōu)化,以確保其在特定平臺(tái)上的表現(xiàn)符合預(yù)期。
示例代碼
下面是一個(gè)完整的示例代碼,演示了如何使用 mmap() 函數(shù)創(chuàng)建內(nèi)存映射文件對(duì)象,并對(duì)其進(jìn)行讀寫操作。
import mmap
def memory_map():
with open('file.bin', 'r+b') as f:
# 將文件映射到內(nèi)存中
mm = mmap.mmap(f.fileno(), 0)
# 讀取前10個(gè)字節(jié)
data1 = mm[:10]
print(data1)
# 修改前5個(gè)字節(jié)
mm[:5] = b'Hello'
# 查找字符串
pos = mm.find(b'world')
print(pos)
# 替換字符串
mm[pos:pos+5] = b'WORLD'
# 關(guān)閉內(nèi)存映射文件
mm.close()
if __name__ == '__main__':
memory_map()
在上述代碼中,我們使用 mmap() 函數(shù)將文件 file.bin 映射到內(nèi)存中,并獲取了一個(gè)內(nèi)存映射文件對(duì)象 mm。然后,我們可以像處理數(shù)組一樣對(duì)內(nèi)存映射文件進(jìn)行讀寫操作。最后,我們調(diào)用 close() 方法關(guān)閉內(nèi)存映射文件。
大文件分塊讀取
當(dāng)需要處理大型文件時(shí),可能會(huì)遇到內(nèi)存不足的問題。為了解決這個(gè)問題,我們可以將文件分成多個(gè)塊進(jìn)行讀取和處理。這樣可以避免一次性將整個(gè)文件讀入內(nèi)存,從而降低內(nèi)存的使用量。在 Python 中,我們可以使用生成器來實(shí)現(xiàn)大文件分塊讀取。
生成器函數(shù)實(shí)現(xiàn)大文件分塊讀取
def read_in_chunks(file_obj, chunk_size=1024):
"""生成器函數(shù):分塊讀取文件"""
while True:
data = file_obj.read(chunk_size)
if not data:
break
yield data
在上述代碼中,我們定義了一個(gè)生成器函數(shù) read_in_chunks(),該函數(shù)接受兩個(gè)參數(shù):文件對(duì)象和塊大小。在函數(shù)體內(nèi),我們使用 while 循環(huán)從文件中讀取指定大小的數(shù)據(jù)塊,并將其作為生成器對(duì)象的返回值。如果讀取完整個(gè)文件,則退出循環(huán)并返回最后一塊數(shù)據(jù)。
使用生成器函數(shù)讀取大文件
with open('large_file.txt', 'r') as f:
for chunk in read_in_chunks(f, chunk_size=1024):
process_data(chunk)
在上述代碼中,我們使用 with 語句打開文件 large_file.txt,并循環(huán)讀取文件的分塊數(shù)據(jù)。每次循環(huán)迭代時(shí),處理函數(shù) process_data() 將會(huì)被調(diào)用,并將當(dāng)前的數(shù)據(jù)塊作為參數(shù)傳遞進(jìn)去。這樣,在整個(gè)文件讀取完成后,我們可以在 process_data() 函數(shù)內(nèi)部處理所有的數(shù)據(jù)。
注意事項(xiàng)
需要注意以下幾點(diǎn):
- 在使用生成器函數(shù)處理大型文件時(shí),需要根據(jù)實(shí)際情況選擇合適的塊大小。如果塊的大小太小,則會(huì)增加系統(tǒng)的調(diào)用次數(shù);如果塊的大小太大,則可能會(huì)導(dǎo)致內(nèi)存溢出。
- 如果在處理文件結(jié)束后沒有顯式地關(guān)閉文件對(duì)象,則可能會(huì)導(dǎo)致資源泄漏或其他問題。
- 在某些操作系統(tǒng)上,如果文件長(zhǎng)度超過了可用的虛擬內(nèi)存大小,則可能無法完整讀取文件。
示例代碼
下面是一個(gè)完整的示例代碼,演示了如何使用生成器函數(shù)實(shí)現(xiàn)大文件分塊讀取。
def read_in_chunks(file_obj, chunk_size=1024):
"""生成器函數(shù):分塊讀取文件"""
while True:
data = file_obj.read(chunk_size)
if not data:
break
yield data
def process_data(data):
"""處理函數(shù):輸出數(shù)據(jù)塊的長(zhǎng)度"""
print(len(data))
if __name__ == '__main__':
with open('large_file.txt', 'r') as f:
for chunk in read_in_chunks(f, chunk_size=1024):
process_data(chunk)
在上述代碼中,我們定義了一個(gè)生成器函數(shù) read_in_chunks(),用于分塊讀取文件;另外還定義了一個(gè)處理函數(shù) process_data(),用于輸出數(shù)據(jù)塊的長(zhǎng)度。最后,在主程序中,我們使用 with 語句打開文件 large_file.txt 并循環(huán)讀取文件的分塊數(shù)據(jù),并將其作為參數(shù)傳遞給 process_data() 函數(shù)進(jìn)行處理。