Ответ 1
-
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 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 a large file:"
sio.puts
sio.write File.read('/path/to/a/very/big/file')
@result = sio.string
end
end
Добавьте ко всему этому, что эти утечки важны только тогда, когда строки являются большими или экземплярами StringIO многочисленны, и экземпляр StringIO долговечен, и вы можете видеть, что явно закрытие StringIO редко, если когда-либо понадобилось.