原文转载自「刘悦的技术博客」https://v3u.cn/a_id_97

最近无论是面试还是笔试,有一个高频问题始终阴魂不散,那就是给一个大文件,至少超过10g,在内存有限的情况下(低于2g),该以什么姿势读它?

所有人都知道,用python读文件有一套”标准流程“:

def retrun_count(fname):
"""计算文件有多少行
"""
count = 0
with open(fname) as file:
for line in file:
count += 1
return count

为什么这种文件读取方式会成为标准?这是因为它有两个好处:

with 上下文管理器会自动关闭打开的文件描述符
在迭代文件对象时,内容是一行一行返回的,不会占用太多内存

但这套标准做法并非没有缺点。如果被读取的文件里,根本就没有任何换行符,那么上面的第二个好处就不成立了。当代码执行到 for line in file 时,line 将会变成一个非常巨大的字符串对象,消耗掉非常可观的内存。

如果有一个 5GB 大的文件 big_file.txt,它里面装满了随机字符串。只不过它存储内容的方式稍有不同,所有的文本都被放在了同一行里

如果我们继续使用前面的 return_count 函数去统计这个大文件行数。那么在一台pc上,这个过程会足足花掉 65 秒,并在执行过程中吃掉机器 2GB 内存

为了解决这个问题,我们需要暂时把这个“标准做法”放到一边,使用更底层的 file.read() 方法。与直接循环迭代文件对象不同,每次调用 file.read(chunk_size) 会直接返回从当前位置往后读取 chunk_size 大小的文件内容,不必等待任何换行符出现。

所以,如果使用 file.read() 方法,我们的函数可以改写成这样:

def return_count_v2(fname):

    count = 0
block_size = 1024 * 8
with open(fname) as fp:
while True:
chunk = fp.read(block_size)
# 当文件没有更多内容时,read 调用将会返回空字符串 ''
if not chunk:
break
count += 1
return count

在新函数中,我们使用了一个 while 循环来读取文件内容,每次最多读取 8kb 大小,这样可以避免之前需要拼接一个巨大字符串的过程,把内存占用降低非常多。

利用生成器解耦代码

假如我们在讨论的不是 Python,而是其他编程语言。那么可以说上面的代码已经很好了。但是如果你认真分析一下 return_count_v2 函数,你会发现在循环体内部,存在着两个独立的逻辑:数据生成(read 调用与 chunk 判断) 与 数据消费。而这两个独立逻辑被耦合在了一起。

为了提升复用能力,我们可以定义一个新的 chunked_file_reader 生成器函数,由它来负责所有与“数据生成”相关的逻辑。这样 return_count_v3 里面的主循环就只需要负责计数即可。

def chunked_file_reader(fp, block_size=1024 * 8):
"""生成器函数:分块读取文件内容
"""
while True:
chunk = fp.read(block_size)
# 当文件没有更多内容时,read 调用将会返回空字符串 ''
if not chunk:
break
yield chunk def return_count_v3(fname):
count = 0
with open(fname) as fp:
for chunk in chunked_file_reader(fp):
count += 1
return count

进行到这一步,代码似乎已经没有优化的空间了,但其实不然。iter(iterable) 是一个用来构造迭代器的内建函数,但它还有一个更少人知道的用法。当我们使用 iter(callable, sentinel) 的方式调用它时,会返回一个特殊的对象,迭代它将不断产生可调用对象 callable 的调用结果,直到结果为 setinel 时,迭代终止。

def chunked_file_reader(file, block_size=1024 * 8):
"""生成器函数:分块读取文件内容,使用 iter 函数
"""
# 首先使用 partial(fp.read, block_size) 构造一个新的无需参数的函数
# 循环将不断返回 fp.read(block_size) 调用结果,直到其为 '' 时终止
for chunk in iter(partial(file.read, block_size), ''):
yield chunk

最后只需要两行代码,就构造出了一个可复用的分块读取方法,和一开始的”标准流程“按行读取 2GB 内存/耗时 65 秒 相比,使用生成器的版本只需要 7MB 内存 / 12 秒就能完成计算。效率提升了接近 4 倍,内存占用更是不到原来的 1%,简直完美。

原文转载自「刘悦的技术博客」 https://v3u.cn/a_id_97

Python花式读取大文件(10g/50g/1t)遇到的性能问题(面试向)的更多相关文章

  1. Python逐块读取大文件行数的代码 - 为程序员服务

    Python逐块读取大文件行数的代码 - 为程序员服务 python数文件行数最简单的方法是使用enumerate方法,但是如果文件很大的话,这个方法就有点慢了,我们可以逐块的读取文件的内容,然后按块 ...

  2. python 如何读取大文件

    一般的读取文件的方法: with open(file_path, "r") as f: print f.read() 或者 with open(file_path,"r& ...

  3. python json及mysql——读取json文件存sql、数据库日期类型转换、终端操纵mysql及python codecs读取大文件问题

    preface: 近期帮师兄处理json文件,须要读到数据库里面,以备其兴许从数据库读取数据.数据是关于yelp站点里面的: https://github.com/Yelp/dataset-examp ...

  4. Python 之读取大文件readline与readlines的差别

    import time def get_all_lines(filename): start_time = time.time() try: f = open(filename, 'rb') exce ...

  5. 面试题-python 如何读取一个大于 10G 的txt文件?

    前言 用python 读取一个大于10G 的文件,自己电脑只有8G内存,一运行就报内存溢出:MemoryError python 如何用open函数读取大文件呢? 读取大文件 首先可以自己先制作一个大 ...

  6. Python读取大文件的"坑“与内存占用检测

    python读写文件的api都很简单,一不留神就容易踩"坑".笔者记录一次踩坑历程,并且给了一些总结,希望到大家在使用python的过程之中,能够避免一些可能产生隐患的代码. 1. ...

  7. Python读取大文件(GB)

    Python读取大文件(GB) - CSDN博客 https://blog.csdn.net/shudaqi2010/article/details/54017766

  8. 强悍的Python读取大文件的解决方案

    这是一道著名的 Python 面试题,考察的问题是,Python 读取大文件和一般规模的文件时的区别,也即哪些接口不适合读取大文件. 1. read() 接口的问题 f =open(filename, ...

  9. PHP 与Python 读取大文件的区别

    php读取大文件的方法   <?php function readFile($file) { # 打开文件 $handle = fopen($file, 'rb'); while (feof($ ...

随机推荐

  1. [保姆级教程] 如何在 Linux Kernel (V5.17.7) 中添加一个系统调用(System call)

    最近在学习 <linux Kernel Development>,本书用的linux kernel 是v2.6 版本的.看完"系统调用"一节后,想尝试添加一个系统调用, ...

  2. zabbix 1.2

    1.zabbix图形界面乱码问题处理 2.自定义监控项 (1)在agent端配置agent.conf    打开vim /etc/zabbix/zabbix-agent.conf    找到UserP ...

  3. 不可不知的 MySQL 升级利器及 5.7 升级到 8.0 的注意事项

    数据库升级,是一项让人喜忧参半的工程.喜的是,通过升级,可以享受新版本带来的新特性及性能提升.忧的是,新版本可能与老的版本不兼容,不兼容主要体现在以下三方面: 语法不兼容. 语义不兼容.同一个SQL, ...

  4. vue项目引入TinyMCE

    1.安装 npm install @tinymce/tinymce-vue@3.0.1 -S 2.配置 <template> <!-- 富文本 --> <div> ...

  5. 455. Assign Cookies - LeetCode

    Question 455. Assign Cookies Solution 题目大意:数组g的大小表示有几个小孩,每个元素表示小孩的食量,数组s的大小表示有多少个饼干,每个元素的大小表示每个饼干的大小 ...

  6. 个人冲刺(二)——体温上报app(二阶段)

    冲刺任务:完成app首页.第二页面和特殊情况的页面布局 activity_main.xml <?xml version="1.0" encoding="utf-8& ...

  7. Tarjan 连通性

    Tarjan 连通性 Tarjan 爷爷的代表作,图的连通性问题直接解决 两个核心数组: \(dfn_u\):\(u\) 的 dfs 序 \(low_u\):\(u\) 及 \(u\) 的后代通过返祖 ...

  8. Eureka高可用集群搭建

    就是搭建Eureka的集群. 每个Eureka Server需要相互注册,确保数据一致. 我这里准备两个Eureka Server  他两的POM文件配置是一样的 <dependencies&g ...

  9. CSS 技术

    浏览本篇文章前可以先看之前的前端网页介绍和html常用标签以便更容易理解 本文目录: 目录 CSS 技术介绍 CSS 语法规则 CSS 和 HTML 的结合方式 第一种: 第二种 第三种 CSS 选择 ...

  10. 借助SpotBugs将程序错误扼杀在摇篮中

    背景 最近一年多在一家toB业务的公司,部门主要做的是建筑行业的招投标业务信息化,希望借助软件来达到"阳光.降本.提效"的目的,我刚入职时大概30多家客户,截止现在已经超过100家 ...