13

我知道如何写入文件和读取文件,但除了将整个文件读入内存、操作和重写整个文件之外,我不知道如何修改文件。对于大文件,这不是很有效率。

我真的不知道追加和写入之间的区别。

例如

如果我有一个文件包含:

Person1,will,23
Person2,Richard,32
Person3,Mike,44

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

4

4 回答 4

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"
    
  • 进行真正的删除。这意味着该行将不再存在。所以你必须阅读下一行并用它覆盖当前行。然后对以下所有行重复此操作,直到到达文件末尾。这似乎是容易出错的任务(不同长度的行等),所以这里有一个没有错误的替代方法:打开临时文件,写入到(但不包括)要删除的行,跳过你要删除的行要删除,将其余部分写入临时文件。删除原始文件并重命名临时文件以使用其名称。完毕。

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

    优点:没有假设。行被删除。无需更改读取代码。缺点:删除行时需要做更多的工作(不仅是代码,还有 IO/CPU 时间)。

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

于 2013-05-19T19:50:38.413 回答
7

由于文件本质上是作为连续的数据块保存到磁盘的,因此删除其中的任何部分都需要至少重写其后的内容。这实际上意味着 - 正如你所说 - 它对于大文件并不是特别有效。因此,通常最好限制文件大小,以免发生此类问题。

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

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'

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

于 2013-05-19T19:53:45.327 回答
4

您可以打开文件并逐行读取,将要保留的行附加到新文件中。这使您可以最大程度地控制保留哪些行,而不会破坏原始文件。

File.open('output_file_path', 'w') do |output| # 'w' for a new file, 'a' append to existing
  File.open('input_file_path', 'r') do |input|
    line = input.readline
    if keep_line(line) # logic here to determine if the line should be kept
      output.write(line)
    end
  end
end

如果您知道要删除的块的开头和结尾的位置,则可以打开文件,读取到开头,然后查找到结尾并继续阅读。

查找 read 方法的参数,并在此处阅读有关 seek 的信息:

http://ruby-doc.org/core-2.0/IO.html#method-i-read

于 2013-05-19T19:50:41.983 回答
0

在这里阅读:

File.open('output.txt', 'w') do |out_file|
  File.open('input.txt', 'r').each do |line|
    out_file.print line.sub('Person2', '')
  end
end
于 2013-05-19T19:47:17.490 回答