读取 .txt 文件内存效率 Python

Read a .txt file memory efficient in Python

我正在阅读一些 .txt 文件作为列表,使用这种方法:

with open('../Results/DIMP_1120.txt', 'r') as f:
    DIMP_1120 = list(csv.reader(f, delimiter="|"))
with open('../Results/DIMP_1121.txt', 'r') as f:
    DIMP_1121 = list(csv.reader(f, delimiter="|"))
with open('../Results/DIMP_1122.txt', 'r') as f:
    DIMP_1122 = list(csv.reader(f, delimiter="|"))

但这几乎是 RAM 内存中文件大小的 10 倍。

有没有高效的阅读方式?

之后,我将追加这些列表并对其进行排序。

big_list = DIMP_1120 + DIMP_1121 + DIMP_1122

#Order all lists by *Sorter (Row_id2)
from operator import itemgetter
big_list= sorted(big_list, key=itemgetter(0))

所以我想我需要一次将所有列表记入内存。

将数据读取到 list 意味着您正在将所有行加载并保存到内存中。您可以做的是通过迭代 csv.reader() 来逐行迭代,如记录:

csv.reader(csvfile, dialect='excel', **fmtparams)

Return a reader object which will iterate over lines in the given csvfile. csvfile can be any object which supports the iterator protocol and returns a string each time its next() method is called... Each row read from the csv file is returned as a list of strings.

with open('../Results/DIMP_1120.txt', 'r') as f:
    for row in csv.reader(f, delimiter="|")):
        # Process the current line

这样做的缺点是您一次只能访问 1 行。但我相信,如果您不想一次将所有内容加载到内存中,这可能是唯一的方法。您只需要重新设计逻辑来处理每行需要完成的所有事情。

如果您可以一次处理一行数据而不存储每一行​​,例如

for row in csv.reader(f, delimiter="|"):

这样做;这是显着减少峰值内存使用的唯一方法。

否则,你能做的最好的就是在阅读时将行存储格式从 list 转换为 tuple,这应该至少节省一点内存(如果 csv.reader 更多默认情况下不会截断过度分配 list),因为 tuple 不会过度分配,并且它们存储数据与 Python object header (没有过度分配或额外的分配器 round-off 开销),而 list 的 header 只是添加一个指向单独分配的内存的指针(它会过度分配并产生两倍的 round-off 开销);对于大小为 2 的动态分配 list(例如,在 CPython 3.9 中,解包概括的行为类似于顺序追加,[*(0, 1)]),容器开销可以从 120 字节下降到 56 字节(可能此外,由于分配器 round-off 错误对 sys.getsizeof 不可见,并且 list 支付两次,tuple 仅支付一次)只需转换为 tuple,可以对数百万这样的行产生影响。最有效的转换方式是更改:

DIMP_1120 = list(csv.reader(f, delimiter="|"))  

至:

DIMP_1120 = list(map(tuple, csv.reader(f, delimiter="|")))

map 在 Python 3 上延迟操作,因此每一行将被读取为 list,转换为 tuple,并存储在外部 list 在阅读下一个之前;它不会涉及将整个输入同时存储为 lists 和 tuples,即使是片刻。如果您的基础数据有一些字段可以转换 up-front 为更有效的存储类型(例如 int),则 list 可以转换字段并将它们打包为 [=17] =]s 而不是 lists 可以获得更多,例如对于每行四个字段,其中最后三个在逻辑上是 ints,你可以这样做:

DIMP_1120 = [(a, int(b), int(c), int(d)) for a, b, c, d in csv.reader(f, delimiter="|")]
# If you might have some empty/missized rows you wish to ignore, an if check can discard
# wrong length lists; a nested "loop" over the single item can unpack after checking:
DIMP_1120 = [(a, int(b), int(c), int(d)) for lst in csv.reader(f, delimiter="|")
             if len(lst) == 4
             for a, b, c, d in (lst,)]

csv.reader 解压 list,将相关字段转换为 int,并重新打包为 tuple

Side-note:确保将 newline=""(空字符串)传递给您的 open 调用; csv 模块需要它来正确处理来自不同 CSV 方言的换行符。

更新: 读入单独的 lists,然后连接,然后排序,将峰值外部 list 开销从与行数成比例提高到与行数的 2.66 倍成正比(假设所有文件的大小相同)。您可以通过更改来避免这种开销:

with open('../Results/DIMP_1120.txt', 'r') as f:
    DIMP_1120 = list(csv.reader(f, delimiter="|"))  
with open('../Results/DIMP_1121.txt', 'r') as f:
    DIMP_1121 = list(csv.reader(f, delimiter="|"))  
with open('../Results/DIMP_1122.txt', 'r') as f:
    DIMP_1122 = list(csv.reader(f, delimiter="|"))  

big_list = DIMP_1120 + DIMP_1121 + DIMP_1122

#Order all lists by *Sorter (Row_id2)
from operator import itemgetter
big_list= sorted(big_list, key=itemgetter(0))

至:

from itertools import chain

with open('../Results/DIMP_1120.txt', 'r') as f1, \
     open('../Results/DIMP_1121.txt', 'r') as f2, \
     open('../Results/DIMP_1122.txt', 'r') as f3:
    
    ALL_DIMP = chain.from_iterable(csv.reader(f, delimiter="|")
                                   for f in (f1, f2, f3))
    big_list = sorted(map(tuple, ALL_DIMP), key=itemgetter(0))

只制作了一个 list(您的原始代码有六个 list;一个用于每个输入文件,一个用于连接前两个文件,一个用于连接所有文件三个文件,一个新文件用于所有三个文件的排序串联),包含所有数据,并且它是根据 get-go.

排序创建的

我会注意到,这可能在命令行中做得更好,至少在 *NIX-like 系统上,sort 命令行实用程序知道如何按字段对大文件进行排序, 自动溢出到磁盘以避免一次在内存中存储太多。它可以在 Python 中完成,但它会更丑陋(除非有一些我不知道的 PyPI 模块可以做到这一点)。