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