选择 CSV.foreach 或类似方法的起始行?不想将文件加载到内存中

Choose starting row for CSV.foreach or similar method? Don't want to load file into memory

编辑(我调整了标题):我目前正在使用 CSV.foreach 但它从第一行开始。我想在任意行开始读取文件而不将文件加载到内存中。 CSV.foreach 适用于在文件开头检索数据,但不适用于文件末尾我需要的数据。

This answer 与我想要做的类似,但它将整个文件加载到内存中;这是我不想做的。

我有一个 10GB 的文件,key 列按升序排列:

# example 10gb file rows
key,state,name
1,NY,Jessica
1,NY,Frank
1,NY,Matt
2,NM,Jesse
2,NM,Saul
2,NM,Walt
etc..

我找到了我想以这种方式开始的行...

file = File.expand_path('~/path/10gb_file.csv')

File.open(file, 'rb').each do |line|
  if line[/^2,/]
    puts "#{$.}: #{line}" # 5: 2,NM,Jesse
    row_number = $. # 5
    break
  end
end

...我想使用 row_number 并执行类似的操作但不将 10gb 文件加载到内存中:

CSV.foreach(file, headers: true).drop(row_number) { |row| "..load data..." }

最后,我目前正在像下一个片段一样处理它;当行朝向文件的前面时它工作正常,但当它们接近末尾时就不行了。

CSV.foreach(file, headers: true) do |row|
  next if row['key'].to_i < row_number.to_i
  break if row['key'].to_i > row_number.to_i

  "..load data..."
end

我正在尝试使用 CSV.foreach,但我愿意接受建议。我正在考虑的另一种方法,但对于文件中间的数字似乎并不有效:

Foreach 会做你需要的一切。它流式传输,因此适用于大文件。

CSV.foreach('~/path/10gb_file.csv') do |line| 
   # Only one line will be read into memory at a time.
   line

end

跳过我们不感兴趣的数据的最快方法是使用 read 前进到文件的一部分。

File.open("/path/10gb_file.csv") do |f| 
  f.seek(107)  # skip 107 bytes eg. one line. (constant time)
  f.read(50)   # read first 50 on second line
end

我认为你的想法是正确的。既然您已经说过您不担心跨越多行的字段,那么您可以使用 IO 方法查找文件中的特定行并从那里开始解析。以下是您的操作方法:

begin
  file = File.open(FILENAME)

  # Get the headers from the first line
  headers = CSV.parse_line(file.gets)

  # Seek in the file until we find a matching line
  match = "2,"
  while line = file.gets
    break if line.start_with?(match)
  end

  # Rewind the cursor to the beginning of the line
  file.seek(-line.size, IO::SEEK_CUR)

  csv = CSV.new(file, headers: headers)

  # ...do whatever you want...
ensure
  # Don't forget the close the file
  file.close
end

上面的结果是 csv 将是一个 CSV 对象,其第一行是以 2, 开头的行。

我用一个 8MB(170k 行)的 CSV 文件(来自 Lahman's Baseball Database) and found that it was much, much faster than using CSV.foreach alone. For a record in the middle of the file it was about 110x faster, and for a record toward the end about 66x faster. If you want, you can take a look at the benchmark here: https://gist.github.com/jrunning/229f8c2348fee4ba1d88d0dffa58edb7

显然 8MB 与 10GB 完全不同,所以无论如何这都会花费您很长时间。但我很确定这对您来说会快很多,同时还能实现您不一次将所有数据读入文件的目标。