Необходимо ли закрывать каждый вложенный OutputStream и Writer отдельно?
Я пишу кусок кода:
OutputStream outputStream = new FileOutputStream(createdFile);
GZIPOutputStream gzipOutputStream = new GZIPOutputStream(outputStream);
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(gzipOutputStream));
мне нужно закрыть каждый поток или писатель, как показано ниже?
gzipOutputStream.close();
bw.close();
outputStream.close();
или просто закрытие последнего потока будет в порядке?
bw.close();
7 ответов
предполагая, что все потоки будут созданы хорошо, да, просто закрывая bw
нормально С этими реализациями потока; но это большое предположение.
Я хотел бы использовать try-with-resources (учебник) так что любые проблемы с построением последующих потоков, которые вызывают исключения, не оставляют предыдущие потоки висящими, и поэтому вам не нужно полагаться на реализацию потока, имеющую вызов для закрытия базового поток:
try (
OutputStream outputStream = new FileOutputStream(createdFile);
GZIPOutputStream gzipOutputStream = new GZIPOutputStream(outputStream);
OutputStreamWriter osw = new OutputStreamWriter(gzipOutputStream);
BufferedWriter bw = new BufferedWriter(osw)
) {
// ...
}
обратите внимание, что вы больше не звоните close
на всех.
важное замечание: чтобы попробовать с ресурсами закрыть их, вы должны назначьте потоки переменным по мере их открытия, вы не можете использовать вложенность. Если вы используете вложенность, исключение при построении одного из более поздних потоков (скажем,GZIPOutputStream
) оставит открытым любой поток, построенный вложенными вызовами внутри него. От JLS §14.20.3:
оператор try-with-resources параметризован с помощью переменные (известные как ресурсы), которые инициализируются перед выполнением
try
блокируются и закрываются автоматически, в обратном порядке, из которого они были инициализированы, после выполненияtry
блок.
обратите внимание на слово "переменные" (Курсив мой).
Е. Г., не делай этого:
// DON'T DO THIS
try (BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(
new GZIPOutputStream(
new FileOutputStream(createdFile))))) {
// ...
}
...потому что исключение из GZIPOutputStream(OutputStream)
конструктор (который говорит, что может бросить IOException
и записывает заголовок в базовый поток) оставит FileOutputStream
открыть. Поскольку у некоторых ресурсов есть конструкторы, которые могут бросать, а у других нет, хорошая привычка просто перечислять их отдельно.
мы можем дважды проверить нашу интерпретацию этого раздела JLS с помощью этой программы:
public class Example {
private static class InnerMost implements AutoCloseable {
public InnerMost() throws Exception {
System.out.println("Constructing " + this.getClass().getName());
}
@Override
public void close() throws Exception {
System.out.println(this.getClass().getName() + " closed");
}
}
private static class Middle implements AutoCloseable {
private AutoCloseable c;
public Middle(AutoCloseable c) {
System.out.println("Constructing " + this.getClass().getName());
this.c = c;
}
@Override
public void close() throws Exception {
System.out.println(this.getClass().getName() + " closed");
c.close();
}
}
private static class OuterMost implements AutoCloseable {
private AutoCloseable c;
public OuterMost(AutoCloseable c) throws Exception {
System.out.println("Constructing " + this.getClass().getName());
throw new Exception(this.getClass().getName() + " failed");
}
@Override
public void close() throws Exception {
System.out.println(this.getClass().getName() + " closed");
c.close();
}
}
public static final void main(String[] args) {
// DON'T DO THIS
try (OuterMost om = new OuterMost(
new Middle(
new InnerMost()
)
)
) {
System.out.println("In try block");
}
catch (Exception e) {
System.out.println("In catch block");
}
finally {
System.out.println("In finally block");
}
System.out.println("At end of main");
}
}
...который имеет выход:
Constructing Example$InnerMost Constructing Example$Middle Constructing Example$OuterMost In catch block In finally block At end of main
обратите внимание, что нет вызовов close
там.
если мы фиксируем main
:
public static final void main(String[] args) {
try (
InnerMost im = new InnerMost();
Middle m = new Middle(im);
OuterMost om = new OuterMost(m)
) {
System.out.println("In try block");
}
catch (Exception e) {
System.out.println("In catch block");
}
finally {
System.out.println("In finally block");
}
System.out.println("At end of main");
}
тогда мы получим соответствующую close
вызовы:
Constructing Example$InnerMost Constructing Example$Middle Constructing Example$OuterMost Example$Middle closed Example$InnerMost closed Example$InnerMost closed In catch block In finally block At end of main
(да, два звонка в InnerMost#close
правильно, один из Middle
, другой из try-with-resources.)
вы можете закрыть внешний самый поток, на самом деле вам не нужно сохранять все потоки, обернутые, и вы можете использовать Java 7 try-with-resources.
try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
new GZIPOutputStream(new FileOutputStream(createdFile)))) {
// write to the buffered writer
}
если вы подписались на YAGNI, или вы-aint-gonna-need-it, вы должны только добавлять код, который вам действительно нужен. Вы не должны добавлять код, который вам может понадобиться, но на самом деле ничего полезного не делает.
возьмите этот пример и представьте, что может пойти не так, если вы этого не сделали, и что удар будет?
try (
OutputStream outputStream = new FileOutputStream(createdFile);
GZIPOutputStream gzipOutputStream = new GZIPOutputStream(outputStream);
OutputStreamWriter osw = new OutputStreamWriter(gzipOutputStream);
BufferedWriter bw = new BufferedWriter(osw)
) {
// ...
}
давайте начнем с FileOutputStream, который вызывает open
чтобы сделать всю реальную работу.
/**
* Opens a file, with the specified name, for overwriting or appending.
* @param name name of file to be opened
* @param append whether the file is to be opened in append mode
*/
private native void open(String name, boolean append)
throws FileNotFoundException;
если файл не найден, нет базового ресурса для закрытия, поэтому закрытие его не будет иметь никакого значения. Если файл существует, он должен вызывать исключение FileNotFoundException. Таким образом, нет ничего, что можно получить, пытаясь закрыть ресурс только из этой линии.
причина, вы должны закрыть файл при открытии файла успешно, но позже вы получите сообщение об ошибке.
давайте посмотрим на следующий поток GZIPOutputStream
есть код, который может выбросить исключение
private void writeHeader() throws IOException {
out.write(new byte[] {
(byte) GZIP_MAGIC, // Magic number (short)
(byte)(GZIP_MAGIC >> 8), // Magic number (short)
Deflater.DEFLATED, // Compression method (CM)
0, // Flags (FLG)
0, // Modification time MTIME (int)
0, // Modification time MTIME (int)
0, // Modification time MTIME (int)
0, // Modification time MTIME (int)
0, // Extra flags (XFLG)
0 // Operating system (OS)
});
}
это записывает заголовок файла. Теперь было бы очень необычно, чтобы вы могли открыть файл для записи, но не могли записать в него даже 8 байт, но давайте представим, что это может произойти, и мы не закрываем файл после этого. Что происходит с файлом, если он не закрыт?
вы не получите любые незаполненные записи, они отбрасываются, и в этом случае нет успешно записанных байтов в поток, который все равно не буферизован на данный момент. Но файл, который не закрыт, не живет вечно, вместо FileOutputStream имеет
protected void finalize() throws IOException {
if (fd != null) {
if (fd == FileDescriptor.out || fd == FileDescriptor.err) {
flush();
} else {
/* if fd is shared, the references in FileDescriptor
* will ensure that finalizer is only called when
* safe to do so. All references using the fd have
* become unreachable. We can call close()
*/
close();
}
}
}
если вы вообще не закрываете файл, он все равно закрывается, просто не сразу (и, как я уже сказал, данные, которые остаются в буфере, будут потеряны таким образом, но на данный момент их нет)
каковы последствия не закрыть файл немедленно? В нормальных условиях вы потенциально теряете некоторые данные, и у вас потенциально заканчиваются файловые дескрипторы. Но если у вас есть система, в которой вы можете создавать файлы, но ничего не можете им написать, у вас есть большая проблема. т. е. трудно представить, почему вы неоднократно пытаетесь создать этот файл, несмотря на то, что вы терпите неудачу.
оба OutputStreamWriter и BufferedWriter не бросают IOException в своих конструкторах, поэтому не ясно, какая проблема они вызовут. В случае BufferedWriter вы можете получить OutOfMemoryError. В этом случае он немедленно запустит GC, который, как мы видели, все равно закроет файл.
Если все потоки были созданы, то закрытие только самого внешнего просто отлично.
документация на Closeable
интерфейс утверждает, что метод close:
закрывает этот поток и освобождает все связанные с ним системные ресурсы.
высвобождающие системные ресурсы включают в себя закрывающие потоки.
в нем также говорится, что:
Если поток уже закрыт, то вызов этот метод не имеет эффекта.
поэтому, если вы закроете их явно после этого, ничего плохого не произойдет.
Я бы предпочел использовать try(...)
синтаксис (Java 7), например
try (OutputStream outputStream = new FileOutputStream(createdFile)) {
...
}
все будет хорошо,если вы закроете только последний поток-вызов закрытия будет отправлен в базовые потоки.
нет, самый верхний уровень Stream
или reader
гарантирует, что все базовый потоки / считыватели закрыты.
Регистрация close()
метод реализация вашего потока верхнего уровня.
в Java 7, есть функция try-with-resources. Вам не нужно явно закрывать свои потоки, он позаботится об этом.