随机化1.53亿行文件而不会耗尽内存

问题描述 投票:2回答:4

您好我想随机化153mil行文本文件的行,但我使用的当前方式使我在执行时耗尽内存:

with open(inputfile,'r') as source:
    data = [ (random.random(), line) for line in source ]
    data.sort()
with open(outputfile,'w') as target:
    for _, line in data:
        target.write( line )
python python-3.x
4个回答
0
投票

如果不在内存中读取数据并且没有太多复杂性,我将如何做到这一点:

首先计算文件中的最大行长度(使用二进制模式)

with open(inputfile,'rb') as source:
    max_line_len = max(len(line) for line in source)

然后使用正确的填充将另一个文件写入磁盘,因此每行具有完全相同的大小(您需要两倍以上的大小,但因为您没有内存......)。同时计算行数。

with open(inputfile,'rb') as source, open(outputfile,'wb') as dest:
    for count,line in enumerate(source):
        dest.write(line + b"*"*(max_line_len-len(line))) # write padded

您刚刚创建了一个更大的文件,但现在这些行的长度完全相同。好吧,我们在换行后填充,这将在以后有用。示例输出将是(例如,如果max len = 20):

the first line
****the second line
***another line
******

(不确定添加的星的确切数量,但你明白了,请注意填充字符无关紧要,只要它不是\n

这意味着您可以通过max_line_len(如记录文件或数据库)的简单乘法来查找任何行的开头

现在您可以生成行索引列表:

indexes = list(range(count+1))
random.shuffle(indexes)

现在迭代这个索引列表,寻找正确的位置,读取一个块并使用我们在换行后填充的事实进行拆分,所以现在我们可以拆分它以丢弃填充内容。

with open(outputfile,'rb') as source:
   for idx in indexes:
       source.seek(idx * max_line_len)
       random_line = source.read(max_line_len).decode().split("\n")[0]
       print(random_line) # or store to another file

我没有测试过这个但是如果你有足够的磁盘它应该可以工作。当然,如果你有一条非常长的线,这是非常浪费的,其余的很短。


3
投票

使用h5py,您可以将数据文件移植到HDF5格式,然后随机化:

https://stackoverflow.com/a/44866734/3841261

您可以使用random.shuffle(数据集)。对于配备Core i5处理器,8 GB RAM和256 GB SSD的笔记本电脑上的30 GB数据集,这需要11分钟多一点


2
投票

做一些粗略的卫生巾计算,估计每行120个字符乘以153 M行...大约18.5 GB的数据。 (我假设每个字符有1个字节,但它更多的是由于Unicode ...但是你得到了重点)。所以你需要至少那么多的RAM才能完全阅读文本。这就是你在读入时获得内存不足的原因。

您可以采取的一种方法是将作业分成块。读入文件的一部分,随机化这些文件,然后附加到一个新文件,写入文件并清除内存。当然,问题是你只能在特定的块中随机化。

你可以在这里采取许多方法,但如果你没有内存,就无法解决所有文本都无法读取的问题。

编辑

我非常喜欢Chad使用h5py和HDF5的想法。它基本上是在硬盘驱动器上的文件中进行所有的洗牌......有点像强制硬盘驱动器交换,但有更多的控制。我喜欢!它确实需要h5py。


1
投票

我已经发布了an answer,但它确实是次优的,因为它意味着创建另一个文件。

这是一个更简单的解决方案:

  • 第一次完全读取文件,但只存储每行开头的偏移量(与文件的实际内容相比没有很多内存,请参阅答案末尾的估计)
  • 洗牌那些抵消
  • 现在再次打开文件,并寻找每个洗牌的偏移量,一次读取一行

我们必须使用二进制模式,否则我们可能会遇到自动行尾转换问题。

import random

current_offset = 0
offsets = []
with open("input.txt","rb") as f:
    for line in f:
        offsets.append(current_offset)
        current_offset += len(line)

offsets.pop() # remove last offset (end of file)

random.shuffle(offsets)

with open("input.txt","rb") as f:
    for offset in offsets:
        f.seek(offset)
        print(f.readline().decode().rstrip  # or write to another file

对于1.53亿行,你仍然需要大约1到1.5千兆字节的RAM来存储索引(python 3使用长整数,你可以将它存储在numpy数组中而不是减少内存)。如果这是可以接受的,这是一个非常简单的解决方案。

© www.soinside.com 2019 - 2024. All rights reserved.