目录

AaronJny

诗酒繁华,书剑天涯。

X

使用Python读取大文件

今天有个朋友问了我一个问题,如何使用 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)

我们可以运行测试一下,三种代码的输出结果是完全一致的,以下为输出结果:

image.png

因为是直接输出的字节编码,所以可能不是很直观,我们改一下输出的部分:

for e in get_lines('replace_content.py'):
    # 因为print本身就会换行,所以我把每一行最后的换行符去掉了
    print(e.decode('utf8')[:-1])

image.png

ok,到这里就结束了,希望对你有所帮助。


标题:使用Python读取大文件
作者:AaronJny
地址:https://aaronjny.com/articles/2019/11/22/1574405631522.html