是否有必要关闭ruby中的StringIO?
我们是否需要在Ruby中使用后关闭StringIO对象以释放资源,就像我们使用真正的IO对象一样?
obj = StringIO.new "some string" #... obj.close # <--- Do we need to close it?
提炼我的问题
关闭File对象是必要的,因为它将关闭文件描述符。 操作系统中打开的文件数量有限,这就是关闭文件的必要原因。 但是,如果我理解正确,StringIO是内存中的抽象。 我们需要关闭吗?
-
StringIO#close
不释放任何资源或删除对累积字符串的引用。 因此,调用它对资源使用没有影响。 -
只有在垃圾收集期间调用的
StringIO#finalize
释放对累积字符串的引用,以便可以释放它(前提是调用者不保留自己的引用)。 -
StringIO.open
,它简要地创建一个StringIO实例,在返回后不保留对该实例的引用; 因此,可以释放StringIO对累积字符串的引用(前提是调用者不保留对它的引用)。 -
实际上,使用StringIO时很少需要担心内存泄漏。 一旦你完成了它们,就不要再坚持对StringIO的引用,一切都会好的。
潜入源头
StringIO实例使用的唯一资源是它正在累积的字符串。 你可以在stringio.c中看到它(MRI 1.9.3); 这里我们看到了包含StringIO状态的结构:
static struct StringIO *struct StringIO { VALUE string; long pos; long lineno; int flags; int count; };
当StringIO实例完成(即收集垃圾)时,将删除对字符串的引用,以便在没有其他引用的情况下对字符串进行垃圾回收。 这是finalize方法,也是由StringIO#open(&block)
调用以关闭实例。
static VALUE strio_finalize(VALUE self) { struct StringIO *ptr = StringIO(self); ptr->string = Qnil; ptr->flags &= ~FMODE_READWRITE; return self; }
只有在对象被垃圾回收时才会调用finalize方法。 StringIO没有其他方法可以释放字符串引用。
StringIO#close
只设置一个标志。 它不会释放对累积字符串的引用或以任何其他方式影响资源使用:
static VALUE strio_close(VALUE self) { struct StringIO *ptr = StringIO(self); if (CLOSED(ptr)) { rb_raise(rb_eIOError, "closed stream"); } ptr->flags &= ~FMODE_READWRITE; return Qnil; }
最后,当您调用StringIO#string
,您将获得对StringIO实例累积的完全相同的字符串的引用:
static VALUE strio_get_string(VALUE self) { return StringIO(self)->string; }
使用StringIO时如何泄漏内存
所有这些意味着StringIO实例只有一种方法可以导致资源泄漏:您不能关闭StringIO对象,并且必须保留它比保留调用StringIO#string
时获得的字符串更长。 例如,假设一个具有StringIO对象作为实例变量的类:
class Leaker def initialize @sio = StringIO.new @sio.puts "Here's a large file:" @sio.puts @sio.write File.read('/path/to/a/very/big/file') end def result @sio.string end end
想象一下,这个类的用户获取结果,简单地使用它,然后丢弃它,然后保留对Leaker实例的引用。 您可以看到Leaker实例通过未关闭的StringIO实例保留对结果的引用。 如果文件非常大,或者存在许多现存的Leaker实例,则可能会出现问题。 这个简单(故意病态)的例子可以通过简单地不将StringIO保持为实例变量来解决。 当你可以(而且你几乎总是可以)时,最好简单地扔掉StringIO对象,而不是明确地关闭它:
class NotALeaker attr_reader :result def initialize sio = StringIO.new sio.puts "Here's a large file:" sio.puts sio.write File.read('/path/to/a/very/big/file') @result = sio.string end end
除此之外,这些泄漏只在字符串很大或StringIO实例很多且 StringIO实例很长时才有用,你可以看到显然关闭StringIO很少,如果有的话,也是如此。
关闭File
,重要的是系统具有有限数量的描述符(假设您在UNIX上,我不知道Windows的用途)。 使用StringIO
,还有另一个资源:内存。 如果StringIO
对象有很多要存储的内容,那么堆中会有很多内存。 另一方面,垃圾收集器总是关闭它声称的IO
对象,但这假设您有一种优雅的方式将对象放在范围之外。 此外,在负载很重的系统上,物理RAM比文件描述符更有价值。 在我的Linux机器上,178203是最大文件描述符。 我怀疑你能达到那个目的。
在这里回答其他答案:调用close
将无助于您节省内存。
require "stringio" sio = StringIO.new sio.print("A really long string") sio.close sio.string # => "A really long string"
只要sio
做, close
或不close
,“非常长的字符串”将保持不变。
那么为什么StringIO有一个close
方法呢? 鸭打字。 如果您尝试从中读取或写入,则提供close
,并抛出IOError,确保它像真正的File
对象一样。 如果您在unit testing时将其用作模拟对象,则此选项非常有用。
一般来说,答案是否定的。 当垃圾收集器声明时,I / O流会自动关闭。 对文件I / O也有相同的答案。
不,但它有利于内存优化