16

実際のIOオブジェクトの場合と同様に、リソースを解放するために、Rubyで使用した後にStringIOオブジェクトを閉じる必要がありますか?

obj = StringIO.new "some string"
#...
obj.close # <--- Do we need to close it?

私の質問を洗練する

ファイル記述子を閉じるため、Fileオブジェクトを閉じる必要があります。OSでは開くファイルの数が限られているため、ファイルを閉じる必要があります。しかし、私が正しく理解していれば、StringIOはメモリ内の抽象化です。それを閉じる必要がありますか?

4

5 に答える 5

22
  • 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を明示的に閉じる必要はほとんどないことがわかります。

于 2012-04-11T14:14:53.533 に答える
3

を閉じるときFileは、システムの記述子の数が限られているため重要です(UNIXの場合、Windowsが何をするのかわかりません)。を使用StringIOすると、もう1つのリソースが問題になります。メモリです。StringIOオブジェクトに格納するものがたくさんある場合、ヒープから要求されるメモリがたくさんあります。一方、ガベージコレクターは、IO主張するオブジェクトを常に閉じますが、それは、オブジェクトをスコープ外に置くためのエレガントな方法があることを前提としています。また、負荷の高いシステムでは、物理RAMはファイル記述子よりもはるかに価値があります。私のLinuxボックスでは、178203が最大ファイル記述子です。私はあなたがそれに達することができるとは思わない。

于 2012-04-11T13:56:30.283 に答える
3

ここでの他の答えに応えて:呼び出すclose ことはあなたがメモリを節約するのを助けません。

require "stringio"
sio = StringIO.new
sio.print("A really long string")
sio.close
sio.string # => "A really long string"

「本当に長い文字列」は、同じくらい長く続くかsiocloseまたはありませんclose

では、なぜStringIOにcloseメソッドがあるのでしょうか。ダックタイピング。を提供closeし、そこから読み取りまたは書き込みを行おうとした場合にIOErrorをスローすると、実際のFileオブジェクトのように動作することが保証されます。これは、単体テスト中にモックオブジェクトとして使用する場合に便利です。

于 2012-04-12T09:04:13.403 に答える
0

一般的に、答えはノーです。ガベージコレクタによって要求されると、I/Oストリームは自動的に閉じられます。ファイルI/Oについても同じ答えです。

于 2012-04-11T13:13:16.537 に答える
-2

いいえ、ただしメモリの最適化には適しています

于 2012-04-11T13:14:26.583 に答える