今天有個朋友問了我一個問題,如何使用Python讀取大文件?覺得這個問題挺有意思的,就記錄下來。
大部分時間我們處理小文件的時候(1g以內?),可以直接用f.read()或readlines()直接把全部內容讀取到內存裏面來。但當文件非常大,比如10g,100g的時候,文件的大小一般已經超出了機器的內存大小,就沒法直接按小文件的方式處理了。那應該怎麼辦呢?
首先,選一個文件做演示,就用上一篇博客的代碼吧。我們現在有一個文件,名爲replace_content.py
。我們要做的事是讀取並打印這個文件的每一行。這個文件可能太小了,但不影響我們演示大文件的讀取。
一、讀取小文件
我們先看一下讀取小文件的時候是怎麼做的:
def get_lines(file_path):
"""
給定一個文件路徑,讀取文件並返回一個迭代器,這個迭代器將順序返回文件的每一行
"""
with open(file_path, 'rb') as f:
lines = f.readlines()
return lines
for e in get_lines('replace_content.py'):
print(e)
這個很簡單,沒什麼好說的,只是貼上來作爲參考。
二、讀取大文件
讀取大文件有幾種方法,我們逐個來說一下,不過都是通過生成器實現的。
1. f.read(block_size)
在Python中,文件對象有一個方法f.read(block_size),可以用於從文件流中讀取block_size大小的字節塊。那麼,我們只要每次從文件中讀取一點,保證內存裝得下就行了。一個循環下去,總有全部讀取完的一天。
但是,我們還有個要求是,每次輸出文件中的一行。而f.read(block_size)是按字節大小算的,並不會理會文件有沒有換行。所以,我們需要手動處理。
# -*- coding: utf-8 -*-
# @File : read_big_file.py
# @Author: AaronJny
# @Date : 2019/11/22
# @Desc :
def get_lines(file_path):
"""
給定一個文件路徑,讀取文件並返回一個迭代器,這個迭代器將順序返回文件的每一行
"""
with open(file_path, 'rb') as f:
while True:
# 用於緩存一行字節數據
file_bytes = []
# 每次讀取一個字節
while True:
block = f.read(1)
# 判斷讀取到的是不是換行
if block and block != b'\n':
# 不是換行,就說明這一行還沒有讀完,就加入到緩存列表中
file_bytes.append(block)
else:
# 否則的話,這一行緩存完了,可以返回了
break
# 判斷這次讀取一行有沒有讀取到內容
if block:
# 讀取到了
file_bytes.append(b'\n')
# yield一下
yield b''.join(file_bytes)
else:
# 文件已經讀取完啦
break
for e in get_lines('replace_content.py'):
print(e)
2. f.readline()
第一個示例的實現比較繁瑣,但其實Python中的文件對象已經封裝了類似的方法,那就是f.readline()。我們現在用f.readline()來實現一下:
# -*- coding: utf-8 -*-
# @File : read_big_file.py
# @Author: AaronJny
# @Date : 2019/11/22
# @Desc :
def get_lines(file_path):
"""
給定一個文件路徑,讀取文件並返回一個迭代器,這個迭代器將順序返回文件的每一行
"""
with open(file_path, 'rb') as f:
while True:
# 讀取一行
line = f.readline()
# 判斷有沒有讀取到
if line:
# 讀取到了,yield
yield line
# 文件讀取完了
else:
break
for e in get_lines('replace_content.py'):
print(e)
是不是簡單多了?但這仍然不是最好的方法。請往下看。
3.把f作爲迭代對象
事實上,Python中的文件對象是可以被直接作爲可迭代對象的,文件的IO緩衝和內存管理對我們來說是透明的,解釋器會自行處理,我們可以不用理會。
那麼,這個問題可以簡單地寫成這樣:
# -*- coding: utf-8 -*-
# @File : read_big_file.py
# @Author: AaronJny
# @Date : 2019/11/22
# @Desc :
def get_lines(file_path):
"""
給定一個文件路徑,讀取文件並返回一個迭代器,這個迭代器將順序返回文件的每一行
"""
with open(file_path, 'rb') as f:
for line in f:
yield line
for e in get_lines('replace_content.py'):
print(e)
我們可以運行測試一下,三種代碼的輸出結果是完全一致的,以下爲輸出結果:
因爲是直接輸出的字節編碼,所以可能不是很直觀,我們改一下輸出的部分:
for e in get_lines('replace_content.py'):
# 因爲print本身就會換行,所以我把每一行最後的換行符去掉了
print(e.decode('utf8')[:-1])
ok,到這裏就結束了,希望對你有所幫助。