大CSV文件字符串匹配内存溢出:如何实现逐行双循环处理
解决大CSV文件匹配的内存溢出问题
太懂这种头疼的情况了——把几百万条记录一股脑塞进列表里,内存直接就扛不住了对吧?原代码的匹配逻辑没问题,但一次性加载整个大文件到内存的思路肯定要改,咱们换个低内存+高效匹配的路子来解决:
方案一:适合小文件+超大文件的场景(优先推荐)
如果你的small.csv本身不大(能轻松放进内存),那咱们先把需要匹配的键从small.csv里提取出来存到集合里(集合的查找速度是O(1),比循环遍历快N倍),然后逐行读取大文件,每读一行就检查是否匹配,完全不用把大文件加载到内存里:
import csv # 第一步:把small.csv里的匹配键存到集合(内存占用极小) match_keys = set() with open('small.csv', 'r') as s: sml_reader = csv.reader(s) for row in sml_reader: # 提取small.csv第0列作为匹配键,加入集合 match_keys.add(row[0]) # 第二步:逐行读取large.csv,实时检查匹配 with open('large.csv', 'r') as l: lrg_reader = csv.reader(l) for rowl in lrg_reader: # 检查large.csv第7列是否在匹配集合中 if rowl[7] in match_keys: print(rowl[7], rowl[2])
这个方案的内存占用只取决于small.csv的键数量,大文件全程逐行处理,内存压力极低,就算large.csv有几千万条记录也能轻松应对。
方案二:适合两个文件都超大的场景
如果small.csv也大到没法全部存进集合,那咱们可以借助轻量数据库来做JOIN(比如Python自带的SQLite,不用额外安装),数据库会自动处理内存和磁盘的交换,不用咱们手动操心:
import csv import sqlite3 # 创建临时内存数据库(也可以指定磁盘路径,进一步降低内存压力) conn = sqlite3.connect(':memory:') cursor = conn.cursor() # 创建small表并导入匹配键 cursor.execute('CREATE TABLE small (key TEXT PRIMARY KEY)') with open('small.csv', 'r') as s: sml_reader = csv.reader(s) # 用生成器逐行导入,避免一次性加载 cursor.executemany('INSERT OR IGNORE INTO small VALUES (?)', (row[0] for row in sml_reader)) # 创建large表并导入需要的列(只存有用的字段,减少数据量) cursor.execute('CREATE TABLE large (col2 TEXT, col7 TEXT)') with open('large.csv', 'r') as l: lrg_reader = csv.reader(l) cursor.executemany('INSERT INTO large VALUES (?, ?)', ((row[2], row[7]) for row in lrg_reader)) # 执行JOIN查询,获取匹配结果 cursor.execute('SELECT large.col7, large.col2 FROM large JOIN small ON large.col7 = small.key') for result in cursor.fetchall(): print(result[0], result[1]) # 关闭数据库连接 conn.close()
这个方案就算两个文件都超大也能稳定处理,SQLite会自动优化存储和查询效率,比手动写循环靠谱多了。
原代码内存溢出的根源
原代码里lstl = [tuple(row) for row in lrg]会把large.csv的所有记录都转换成元组并存储到列表中,每条记录哪怕只有10个字段,几百万条下来内存占用也会飙升到几个G,直接触发内存溢出。而逐行处理的逻辑,每次只在内存里保留一条记录,处理完就释放,内存占用始终维持在很低的水平。
内容的提问来源于stack exchange,提问作者David Cowburn
相关产品推荐
相关产品推荐

