【问题标题】:Randomize 153 million line file without running out of memory随机化 1.53 亿行文件而不会耗尽内存
【发布时间】:2018-08-31 04:04:28
【问题描述】:

您好,我想随机化一个 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 )

【问题讨论】:

  • 内存异常发生在哪里?是在您阅读文本的第一行吗?
  • 你想重复选择还是只随机选择?
  • @sophros 仅在将行放入数据阵列时对 KeithL 进行排序

标签: python python-3.x


【解决方案1】:

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

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

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

【讨论】:

    【解决方案2】:

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

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

    您可以在这里采取许多方法,但如果您没有记忆力,您就无法一次阅读所有文本。

    编辑

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

    【讨论】:

    • 还有另一种方法:生成一个随机索引列表,并根据需要逐行读取文件多次(不存储)并在正确的行处停止。当然,这将永远持续......因为可能读取 153M*(153M+1)/2 最大次数......
    • 是的,Jean 真是个聪明的主意!
    【解决方案3】:

    我已经发布了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 数组中以减少内存)。如果这是可以接受的,这是一个非常简单的解决方案。

    【讨论】:

      【解决方案4】:

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

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

      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
      

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

      【讨论】:

      • 有些行在原来有 10+ 个字符时输出单个字符
      • @Thiplol 哎呀抱歉:2 个错误:writeline => 写入,并且查找偏移量应乘以 max_line_len。可以重试吗?
      • 感谢接受,但我想我最终可以提出一个更好的答案。几天后我会尝试@Thiplol
      猜你喜欢
      • 2015-03-18
      • 2021-02-10
      • 1970-01-01
      • 2011-04-05
      • 2018-10-17
      • 2022-11-11
      • 1970-01-01
      • 2010-09-15
      • 2019-08-03
      相关资源
      最近更新 更多