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

时间:2018-08-24 19:46:44

标签: python python-3.x

你好,我想随机化一个1.53亿行文本文件的行,但是我目前使用的方式使我在执行此操作时内存不足:

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 )

4 个答案:

答案 0 :(得分:3)

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

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

  

您可以使用random.shuffle(dataset)。这需要超过11点   使用Core i5处理器的笔记本电脑上30 GB数据集所需的分钟数,为8   GB的RAM和256 GB的SSD

答案 1 :(得分:2)

进行一些粗略的餐巾纸计算,估计每行120个字符乘以1.53亿行...得出大约18.5 GB的数据。 (我假设每个字符1个字节,但是由于Unicode会更多,但是……您明白了这一点)。因此,您至少需要一定数量的RAM才能完全阅读文本。这就是为什么读入时出现内存不足异常的原因。

您可以采用的一种方法是将作业分成多个部分。读入文件的各个部分,将它们随机化,然后追加到新文件中,写入文件并随心清除内存。当然,问题在于您只会在特定的块内进行随机化。

您可以在此处采用多种方法,但是如果您没有足够的存储空间,就无法一次读完所有文本。

编辑

我真的很喜欢乍得使用h5py和HDF5的想法。它实际上是在对硬盘驱动器上的文件进行所有改组……有点像强制进行硬盘驱动器交换,但具有更多控制权。我喜欢!它确实需要有h5py。

答案 2 :(得分: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 GB的RAM来存储索引(python 3使用长整数,可以将其存储在numpy数组中以减少内存)。如果可以接受,这是一个非常简单的解决方案。

答案 3 :(得分: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

您刚刚创建了一个更大的文件,但是现在这些行的长度完全相同。好吧,我们在换行符后 进行了填充,这将在以后有用。示例输出为(例如,如果最大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

我尚未对此进行测试,但是如果您有足够的磁盘,它应该可以工作。当然,如果您的行很长而其余的很短,这将非常浪费。