如何使用Ruby删除文本文件中间的数据行

Sen*_*jai 12 ruby csv file-io ruby-on-rails file

我知道如何写入文件,并从文件中读取,但我不知道如何修改文件,除了将整个文件读入内存,操作它,并重写整个文件.对于大文件,这不是很有效率.

我真的不知道追加和写的区别.

例如

如果我有一个文件包含:

Person1,will,23
Person2,Richard,32
Person3,Mike,44
Run Code Online (Sandbox Code Playgroud)

我怎么能只删除包含Person2的行?

Ser*_*sev 15

您可以通过多种方式删除一行:

  • 模拟删除.也就是说,只用空格覆盖行的内容.稍后,当您阅读并处理文件时,只需忽略这些空行.

    优点:这很简单快捷.缺点:它不是真正的数据删除(文件不缩小),你需要在阅读/处理文件时做更多的工作.

    码:

    f = File.new(filename, 'r+')
    f.each do |line|
      if should_be_deleted(line)
        # seek back to the beginning of the line.
        f.seek(-line.length, IO::SEEK_CUR)
    
        # overwrite line with spaces and add a newline char
        f.write(' ' * (line.length - 1))
        f.write("\n")
      end
    end
    f.close
    
    File.new(filename).each {|line| p line }
    
    # >> "Person1,will,23\n"
    # >> "                  \n"
    # >> "Person3,Mike,44\n"
    
    Run Code Online (Sandbox Code Playgroud)
  • 做真正的删除.这意味着该行将不再存在.因此,您必须阅读下一行并用它覆盖当前行.然后对所有后续行重复此操作,直到到达文件末尾.这似乎是容易出错的任务(不同长度的行等),所以这里是一个无错误的替代方法:打开临时文件,写入它排队(但不包括)你要删除的行,跳过你的行想要删除,将其余部分写入临时文件.删除原始文件并重命名临时文件以使用其名称.完成.

    虽然这在技术上是对文件的完全重写,但它确实与您的要求不同.该文件不需要完全加载到内存.您一次只需要一行.Ruby提供了一种方法:IO#each_line.

    优点:没有假设.线条被删除.阅读代码不需要改变.缺点:删除行时不仅需要更多工作(不仅是代码,还有IO/CPU时间).

    在@ azgult的回答中有一个片段说明了这种方法.


azg*_*ult 6

由于文件基本上是作为连续的数据块保存到磁盘上,因此删除它的任何部分都需要至少重写之后的内容.这实际上意味着 - 正如你所说 - 它对于大文件来说并不是特别有效.因此,限制文件大小通常是一个好主意,这样就不会出现这样的问题.

一些"妥协"解决方案可能是将文件逐行复制到第二个文件,然后移动它以替换第一个文件.这样可以避免将文件加载到内存中,但不会避免任何硬盘访问:

require 'fileutils'

open('file.txt', 'r') do |f|
  open('file.txt.tmp', 'w') do |f2|
    f.each_line do |line|
       f2.write(line) unless line.start_with? "Person2"
    end
  end
end
FileUtils.mv 'file.txt.tmp', 'file.txt'
Run Code Online (Sandbox Code Playgroud)

更有效的是读写打开文件并跳到你要删除的位置,然后将其余的数据移回 - 但这会产生一些非常难看的代码(我不能被要求现在就这样做).