【问题标题】:How do I read the nth line of a file efficiently in Ruby?如何在 Ruby 中有效地读取文件的第 n 行?
【发布时间】:2019-08-04 23:18:20
【问题描述】:

我有一个 2 GiB 的文件,我想读取文件的第一行。 我可以调用返回数组的File#readlines方法,并使用[0]括号语法,at(0),或slice(0)first方法。

但是有一个问题。我的 PC 有 3.7 GiB RAM,使用量从 1.1 GiB 一直到 3.7 GiB。但我想要的只是文件的第一行。有没有有效的方法来做到这一点?

【问题讨论】:

  • 这能回答你的问题吗? How to get a particular line from a file
  • 呃,不,想象一下拥有一百 GB 文件的夸张情况。当你运行tail 100_GB_file 时,tail 将只读取最后 10 行或给定的行。您基本上不需要运行数十亿次迭代并在IO.foreach(file, splitter) 上调用.next(),或者您无法在微不足道的 8 GB RAM 中读取整个文件。我不知道这在 Ruby 中是否可行。但是我已经用 Ruby C Extension 解决了这个问题,尤其是用 C 语言读取文件。这真正解决了我的问题:geeksforgeeks.org/…。但这不是真正的红宝石解决方案......
  • 很高兴了解您的解决方案,但您的问题与尾巴无关。

标签: ruby


【解决方案1】:

IO.foreach 呢?

IO.foreach('filename') { |line| p line; break }

这应该读取第一行,打印它,然后停止。它不会读取整个文件;它一次读取一行。

【讨论】:

  • 谢谢,IO.foreach('hello.txt').first 完美运行!或IO.foreach('hello.txt').take(2).to_a[1] 获取第二行...
【解决方案2】:

您是否尝试过readline 而不是readlines

File.open('file-name') { |f| f.readline }

【讨论】:

  • 是的,它消耗了大量的内存!只有readpartial 不会吃那么多...
  • 因为文件包含 ASCII 文本,我现在可以这样做:ch = ''.tap { |a| File.open('hello.txt') { |x| loop until a.concat(x.readpartial(1))[-1] == ?\n } },不会引起任何内存问题...这将读取第一行。但如果第一行包含换行符,它将占用该空行。 strip 可用于去除多余的前导空格或新行。答案仍然会导致内存问题,但感谢您尝试回答。
【解决方案3】:

我会使用命令行。比如这样:

exec("cat #{filename} | head -#{nth_line} | tail -1")

希望对你有用。

【讨论】:

  • 感谢您的回答。但是在 Ruby 中使用 shell 是一个糟糕的选择。我总是尽量避免这种情况。您正在调用一个单独的二进制文件。此外,一个问题是它不是一种 Ruby 方式。您的 Ruby 系统将具有 IO 和 File 类。但是您的系统可能缺少猫!另一件事是调用二进制文件很慢。我已经对clear 方法和print "\e[2J\e[H\e[3J" 进行了基准测试。两者都做同样的工作,但 ANSI 的速度要快 100k 倍。我只会将这些东西用于 MRuby,但我的问题是用于一般的 Ruby 或 MRI。对不起,但是-1...
  • @S.Goswami 我会删除反对票。 Use your downvotes whenever you encounter an egregiously sloppy, no-effort-expended post, or an answer that is clearly and perhaps dangerously incorrect. 这个答案在功能上是正确的,即使它对您的用例来说不是最佳或完美的,并且在许多情况下会完全按照人们的预期工作。
  • @anothermh,我知道了,但在这种情况下,您的程序依赖于 cat 和 head。您不需要这样做,因为 Ruby 已经为您内置了所有内容。这对使用 MRuby 的人很有帮助。例如,IO.foreach 适用于 Linux、Windows 和 Mac,以及 Android 等,但如果您关注回答者,您将只剩下 Linux / Unix... 而且exec(...) 也会导致您的程序退出执行完命令后... 是的,调用shell是另一种可能,但是如果你做一个读取gig文件的基准测试并读取第一行100K次,你肯定会知道其中的区别!
  • @anothermh,这是正确的答案,但不是 Ruby,对吧?您可以在 Kernel#`` / exec / Kernel#system / IO#popen 等内部使用 Perl / Python / Lua 等,而不是 BASH 脚本,这会更慢但会起作用。这就是为什么我认为没有太多的努力来写答案。它根本不是以 Ruby 的方式思考......
  • 我们可以选择不同意。但我要提醒你,低效和不正确之间,以及“对某些平台正确但对其他平台不正确”和不正确之间是有区别的。
【解决方案4】:

所以我提供了一个可以非常有效地完成这项工作的代码。

首先,我们可以使用IO#each_line 方法。假设我们需要 3,000,000 处的线路:

#!/usr/bin/ruby -w

file = File.open(File.join(__dir__, 'hello.txt'))
final = nil
read_upto = 3_000_000 - 1

file.each_line.with_index do |l, i|
    if i == read_upto
        final = l
        break
    end
end

file.close
p final

使用time shell 内置运行:

[我有一个很大的 hello.txt 文件,里面有 #!/usr/bin/ruby -w #lineno!!]

$ time ruby p.rb
"#!/usr/bin/ruby -w #3000000\n"

real    0m1.298s
user    0m1.240s
sys 0m0.043s

我们也可以很轻松的拿到第一行!你明白了……

其次,扩展另一个mh的答案:

#!/usr/bin/ruby -w

enum = IO.foreach(File.join(__dir__, 'hello.txt'))

# Getting the first line
p enum.first

# Getting the 100th line
# This can still cause memory issues because it
# creates an array out of each line
p enum.take(100)[-1]

# The time consuming but memory efficient way
# reading the 3,000,000th line
# While loops are fastest

index, i = 3_000_000 - 1, 0
enum.next && i += 1 while i < index
p enum.next    # reading the 3,000,000th line

time一起运行:

time ruby p.rb 
"#!/usr/bin/ruby -w #1\n"
"#!/usr/bin/ruby -w #100\n"
"#!/usr/bin/ruby -w #3000000\n"

real    0m2.341s
user    0m2.274s
sys 0m0.050s

可能还有其他方式,例如IO#readpartialIO#sysread 等。但是IO.foreachIO#each_line 是最简单且使用起来相当快的。

希望这会有所帮助!

【讨论】:

    【解决方案5】:

    https://www.rosettacode.org/wiki/Read_a_specific_line_from_a_file#Ruby获取

     seventh_line = open("/etc/passwd").each_line.take(7).last
    

    【讨论】:

      猜你喜欢
      • 2022-08-12
      • 1970-01-01
      • 1970-01-01
      • 2011-01-30
      • 1970-01-01
      • 1970-01-01
      • 2010-12-02
      • 2014-02-06
      • 1970-01-01
      相关资源
      最近更新 更多