obj = StringIO.new "some string" #... obj.close # <--- Do we need to close it?
精炼我的问题
关闭File对象是必要的,因为它将关闭文件描述符.打开的文件的数量在操作系统中受到限制,这就是为什么需要关闭文件.但是,如果我理解正确,StringIO是内存中的抽象.我们需要关闭吗?
解决方法
>只有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很少需要.