是否有必要在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很少需要.

相关文章

validates:conclusion,:presence=>true,:inclusion=>{...
一、redis集群搭建redis3.0以前,提供了Sentinel工具来监控各...
分享一下我老师大神的人工智能教程。零基础!通俗易懂!风趣...
上一篇博文 ruby传参之引用类型 里边定义了一个方法名 mo...
一编程与编程语言 什么是编程语言? 能够被计算机所识别的表...
Ruby类和对象Ruby是一种完美的面向对象编程语言。面向对象编...