【问题标题】:'Failed to allocate memory' error with large array大型数组的“分配内存失败”错误
【发布时间】:2015-01-21 13:16:57
【问题描述】:

我正在尝试将一个大型文本文件(大约 200 万行数字,260MB)导入一个数组,对数组进行编辑,然后将结果写入一个新的文本文件,方法是:

file_data = File.readlines("massive_file.txt")
file_data = file_data.map!(&:strip)
file_data.each do |s|
    s.gsub!(/,.*\z/, "")
end
File.open("smaller_file.txt", 'w') do |f|
    f.write(file_data.map(&:strip).uniq.join("\n"))
end

但是,我收到了错误failed to allocate memory (NoMemoryError)。如何分配更多内存来完成任务?或者,理想情况下,我可以使用另一种方法来避免重新分配内存吗?

【问题讨论】:

  • 如果我是你,我会专注于让这个更加渐进 - 没有必要一口气读完整个文件。
  • 正如@FrederickCheung 建议的那样,您应该一次读取一行输入文件。你可以用IO#foreachIO.foreach("input_file") do |line| ... end来做到这一点。在块中转换line,然后将其附加到输出文件中。
  • 您应该明白,您正在内存中制作数据的多个副本。在您的 f.write 行上,“map”、“uniq”和“join”都将制作数据的完整副本,“uniq”可能会将其删减。尽管如此,当您将其相乘时,260MB 开始累加起来。在这里听取其他人的建议并简单地逐步处理数据。
  • 另一种方法,根据您的需要,可能是使用数据库。 SQLite 应该能够轻松处理这么多数据,而无需担心内存使用。

标签: ruby arrays memory-management


【解决方案1】:

你可以逐行读取文件:

require 'set'
require 'digest/md5'
file_data = File.new('massive_file.txt', 'r')
file_output = File.new('smaller_file.txt', 'w')
unique_lines_set = Set.new

while (line = file_data.gets)
    line.strip!
    line.gsub!(/,.*\z/, "")
    # Check if the line is unique
    line_hash = Digest::MD5.hexdigest(line)
    if not unique_lines_set.include? line_hash
      # It is unique so add its hash to the set
      unique_lines_set.add(line_hash)

      # Write the line in the output file
      file_output.puts(line)
    end
end

file_data.close
file_output.close

【讨论】:

  • 鉴于文件的大小,您应该将unique_lines_array 设为一个集合而不是一个数组。还要考虑为每一行计算该集合哈希码的元素。注意你初始化。 unique_lines_arrayunique_lines_dictionary
  • 是的,你适合一切。感谢您的来信。
  • 更多潜在改进:您可能不需要 MD5 哈希,String#hash 就足够了。另外,你真的是说while (line=file_data)吗?在我看来,您打算使用while (line=file_data.readline),尽管在到达文件末尾时会引发异常。 file_data.each_line do |line| 可能是最好的。您也可以将line_hash = Digest::MD5.hexdigest(line); if not unique_lines_set.include? line_hash; unique_lines_set.add(line_hash); 重写为简单的if unique_lines_set.add? line.hash 最后,不要忘记关闭file_datafile_output
  • 如果我没有遗漏您没有在输出文件的行之间插入\n 的内容,您可能希望使用puts 代替write
  • 我不太确定是否需要 md5 - 使用 32 位哈希码,跨 200 万个值发生冲突的概率非常高(如果在 64 位平台上运行 ruby​​,几率会更好)
【解决方案2】:

您可以尝试一次读取和写入一行:

new_file = File.open('smaller_file.txt', 'w')
File.open('massive_file.txt', 'r') do |file|
  file.each_line do |line|
    new_file.puts line.strip.gsub(/,.*\z/, "")
  end
end
new_file.close

唯一未决的就是找到重复的行

【讨论】:

    【解决方案3】:

    或者,您可以分块读取文件,这比逐行读取要快:

    FILENAME="massive_file.txt"
    MEGABYTE = 1024*1024
    
    class File
      def each_chunk(chunk_size=MEGABYTE) # or n*MEGABYTE
        yield read(chunk_size) until eof?
      end
    end
    
    filedata = ""
    open(FILENAME, "rb") do |f|
      f.each_chunk() {|chunk|
          chunk.gsub!(/,.*\z/, "")
          filedata += chunk
      }
    end
    

    参考:https://stackoverflow.com/a/1682400/3035830

    【讨论】:

    • 遵循这种方法使得删除重复行变得非常困难。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-15
    • 2012-11-30
    • 1970-01-01
    相关资源
    最近更新 更多