Python3讀取和處理超大文件的操作詳解
需求:
小明是一位 Python 初學(xué)者,在學(xué)習(xí)了如何用 Python 讀取文件后,他想要做一個小練習(xí):計算某個文件中數(shù)字字符(0~9)的數(shù)量。
場景1:小文件處理
假設(shè)現(xiàn)在有一個測試用的小文件 small_file.txt,里面包含了一行行的隨機字符串:
feiowe9322nasd9233rl aoeijfiowejf8322kaf9a ...
代碼示例:file_process.py
def count_digits(fname): """計算文件里包含多少個數(shù)字字符""" count = 0 with open(fname) as file: for line in file: for s in line: if s.isdigit(): count += 1 return count fname = "./small_file.txt" print(count_digits(fname))
運行結(jié)果:
# 運行腳本 python3 ./file_process.py # 輸出結(jié)果 13
場景2:大文件處理
假設(shè)現(xiàn)在我們的大文件big_file.txt,大小有5G,且所有的文本都在一行。
大文件 big_file.txt
df2if283rkwefh... <剩余 5 GB 大小> ...
卻發(fā)現(xiàn)同樣的程序花費了一分多鐘才給出結(jié)果,并且整個執(zhí)行過程耗光了筆記本電腦的全部 4G 內(nèi)存。
問題分析:
為什么同一份代碼用于大文件時,效率就會變低這么多呢?原因就藏在小明讀取文件的方法里。
在代碼里所使用的文件讀取方式,可謂 Python 里的“標(biāo)準(zhǔn)做法”:首先用 with open (fine_name) 上下文管理器語法獲得一個文件對象,然后用 for 循環(huán)迭代它,逐行獲取文件里的內(nèi)容。為什么這種文件讀取方式會成為標(biāo)準(zhǔn)?這是因為它有兩個好處:
(1) with 上下文管理器會自動關(guān)閉文件描述符;
(2) 在迭代文件對象時,內(nèi)容是一行一行返回的,不會占用太多內(nèi)存。
不過這套標(biāo)準(zhǔn)做法雖好,但不是沒有缺點。假如被讀取的文件里 根本就沒有任何換行符,那么上面列的第 (2) 個好處就不再成立。缺少換行符以后,程序遍歷文件對象時就不知道該何時中斷,最終只能一次性生成一個巨大的字符串對象,白白消耗大量時間和內(nèi)存。這就是 count_digits() 函數(shù)在處理 big_file.txt 時變得異常緩慢的原因。
要解決這個問題,我們需要把這種讀取文件的“標(biāo)準(zhǔn)做法”暫時放到一邊。
解決方法:
使用 while 循環(huán)加 read() 方法分塊讀取。
除了直接遍歷文件對象來逐行讀取文件內(nèi)容外,我們還可以調(diào)用更底層的 file.read() 方法。與直接用循環(huán)迭代文件對象不同,每次調(diào)用 file.read(chunk_size), 會馬上讀取從當(dāng)前游標(biāo)位置往后 chunk_size 大小的文件內(nèi)容,不必等待任何換行符出現(xiàn)。有了 file.read() 方法的幫助,優(yōu)化后的代碼:
def count_digits_v2(fname): """計算文件里包含多少個數(shù)字字符,每次讀取 8 KB""" count = 0 block_size = 1024 * 8 with open(fname) as file: while True: chunk = file.read(block_size) # 當(dāng)文件沒有更多內(nèi)容時,read 調(diào)用將會返回空字符串 '' if not chunk: break for s in chunk: if s.isdigit(): count += 1 return count fname = "./big_file.txt" print(count_digits_v2(fname))
在新函數(shù)中,我們使用了一個 while 循環(huán)來讀取文件內(nèi)容,每次最多讀 8 KB,程序不再需要在內(nèi)存中拼接長達數(shù)吉字節(jié)的字符串,內(nèi)存占用會大幅降低。
(吉字節(jié)是一種數(shù)據(jù)存儲單位,通常用于表示大容量存儲設(shè)備的容量大小。它等于1024^3(1,073,741,824)字節(jié),或者1,024兆字節(jié)。在計算機領(lǐng)域,常用于描述大型文件、程序或數(shù)據(jù)集的大小,例如硬盤容量、內(nèi)存容量等。)
拓展:用Python讀取超大文件中的部分行
Python文件讀取一直是python的常見用法,通用方法是直接readlines加載所有行。
但是,對于超大文件(如100G的tsv),直接加載所有行會非常慢。
如果是想遍歷整個文件并處理每一行,其實并不需要一次加載所有行。
這里用迭代器的方法來讀取文件:
file_name = 'all_items.tsv' start_line = 110000 end_line = 120000 with open(file_name) as f: for i in range(0, start_line): next(f) lines = [next(f) for i in range(start_line, end_line)] print(len(lines))
對于大文件all_items.tsv,咱們只讀取某一個區(qū)間的行來進行處理。先用迭代器滾動到start_line的位置,再開始讀取。
如果咱們可以利用迭代器,把這個文件的行遍歷一遍(并對行進行處理):
file_name = 'all_items.tsv' def process_line(line): return line with open(file_name) as f: while True: try: line = next(f) process_line(line) except StopIteration: break
用一個while循環(huán)就可以完成從頭到尾行的遍歷。
以上就是Python3讀取和處理超大文件的操作詳解的詳細內(nèi)容,更多關(guān)于Python3讀取和處理超大文件的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Python中Tkinter的面向?qū)ο缶幊虇栴}與解決方案
在Python的GUI開發(fā)中,Tkinter是一個廣泛使用的標(biāo)準(zhǔn)庫,結(jié)合面向?qū)ο缶幊痰乃枷?可以使Tkinter的代碼更加模塊化和易于維護,然而,在實際應(yīng)用中,OOP與Tkinter的結(jié)合也會帶來一些常見的問題,本文將通過具體的代碼案例,分析這些問題,并提供相應(yīng)的解決方案2024-12-12numpy實現(xiàn)神經(jīng)網(wǎng)絡(luò)反向傳播算法的步驟
這篇文章主要介紹了numpy實現(xiàn)神經(jīng)網(wǎng)絡(luò)反向傳播算法的步驟,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12Python數(shù)據(jù)結(jié)構(gòu)與算法中的隊列詳解(1)
這篇文章主要為大家詳細介紹了Python的隊列,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-03-03python命令行解析之parse_known_args()函數(shù)和parse_args()使用區(qū)別介紹
這篇文章主要介紹了python命令行解析之parse_known_args()函數(shù)和parse_args()使用介紹,需要的朋友可以參考下2018-01-01解決python遞歸函數(shù)及遞歸次數(shù)受到限制的問題
這篇文章主要介紹了解決python遞歸函數(shù)及遞歸次數(shù)受到限制的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-06-06Python實現(xiàn)數(shù)據(jù)透視表詳解
今天小編就為大家分享一篇用Python實現(xiàn)數(shù)據(jù)的透視表的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-10-10Python3使用requests包抓取并保存網(wǎng)頁源碼的方法
這篇文章主要介紹了Python3使用requests包抓取并保存網(wǎng)頁源碼的方法,實例分析了Python3環(huán)境下requests模塊的相關(guān)使用技巧,需要的朋友可以參考下2016-03-03