社区微信群开通啦,扫一扫抢先加入社区官方微信群
社区微信群
今天有个朋友问了我一个问题,如何使用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)
这个很简单,没什么好说的,只是贴上来作为参考。
读取大文件有几种方法,我们逐个来说一下,不过都是通过生成器实现的。
在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)
第一个示例的实现比较繁琐,但其实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)
是不是简单多了?但这仍然不是最好的方法。请往下看。
事实上,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,到这里就结束了,希望对你有所帮助。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!