Правильный способ закрытия вложенных потоков и писателей в Java
Примечание. Этот вопрос и большинство его ответов датируются до выпуска Java 7. Java 7 предоставляет функцию автоматического управления ресурсами для этого просто. Если вы используете Java 7 или более позднюю версию, вам следует перейти к ответу Росса Джонсона.
Что считается лучшим, наиболее полным способом закрытия вложенных потоков в Java? Например, рассмотрим настройку:
FileOutputStream fos = new FileOutputStream(...)
BufferedOS bos = new BufferedOS(fos);
ObjectOutputStream oos = new ObjectOutputStream(bos);
Я понимаю, что операция close должна быть застрахована (возможно, с помощью предложения finally). Что меня интересует, так это то, нужно ли явно удостовериться, что вложенные потоки закрыты, или достаточно просто закрыть внешний поток (oos)?
Одна вещь, которую я заметил, по крайней мере, имея дело с этим конкретным примером, состоит в том, что внутренние потоки, кажется, только генерируют FileNotFoundExceptions. Казалось бы, это означает, что технически не нужно беспокоиться о закрытии их, если они потерпят неудачу.
Вот что написал коллега:
Технически, если это было реализовано правильно, закрытия внешнего потока (oos) должно быть достаточно. Но реализация кажется ошибочной.
Пример: BufferedOutputStream наследует close() от FilterOutputStream, который определяет его как:
155 public void close() throws IOException {
156 try {
157 flush();
158 } catch (IOException ignored) {
159 }
160 out.close();
161 }
Однако, если flush() по какой-то причине вызывает исключение во время выполнения, out.close() никогда не будет вызываться. Так что кажется наиболее безопасным (но некрасивым) беспокоиться о закрытии FOS, при котором файл остается открытым.
Каков наилучший, когда вам абсолютно необходимо быть уверенным, подход к закрытию вложенных потоков?
И есть ли какие-нибудь официальные документы по Java/Sun, которые подробно разбираются с этим?
10 ответов
Я обычно делаю следующее. Во-первых, определите класс на основе шаблона-метода, чтобы справиться с беспорядком try/catch
import java.io.Closeable;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
public abstract class AutoFileCloser {
// the core action code that the implementer wants to run
protected abstract void doWork() throws Throwable;
// track a list of closeable thingies to close when finished
private List<Closeable> closeables_ = new LinkedList<Closeable>();
// give the implementer a way to track things to close
// assumes this is called in order for nested closeables,
// inner-most to outer-most
protected final <T extends Closeable> T autoClose(T closeable) {
closeables_.add(0, closeable);
return closeable;
}
public AutoFileCloser() {
// a variable to track a "meaningful" exception, in case
// a close() throws an exception
Throwable pending = null;
try {
doWork(); // do the real work
} catch (Throwable throwable) {
pending = throwable;
} finally {
// close the watched streams
for (Closeable closeable : closeables_) {
if (closeable != null) {
try {
closeable.close();
} catch (Throwable throwable) {
if (pending == null) {
pending = throwable;
}
}
}
}
// if we had a pending exception, rethrow it
// this is necessary b/c the close can throw an
// exception, which would remove the pending
// status of any exception thrown in the try block
if (pending != null) {
if (pending instanceof RuntimeException) {
throw (RuntimeException) pending;
} else {
throw new RuntimeException(pending);
}
}
}
}
}
Note the "pending" exception -- this takes care of the case where an exception thrown during close would mask an exception we might really care about.
Наконец, в конце концов сначала пытается закрыться снаружи любого декорированного потока, поэтому, если у вас есть BufferedWriter, обертывающий FileWriter, мы сначала пытаемся закрыть BuffereredWriter, и если это не удается, все равно попробуйте закрыть сам FileWriter. (Note that the definition of Closeable calls for close() to ignore the call if the stream is already closed)
You can use the above class as follows:
try {
// ...
new AutoFileCloser() {
@Override protected void doWork() throws Throwable {
// declare variables for the readers and "watch" them
FileReader fileReader =
autoClose(fileReader = new FileReader("somefile"));
BufferedReader bufferedReader =
autoClose(bufferedReader = new BufferedReader(fileReader));
// ... do something with bufferedReader
// if you need more than one reader or writer
FileWriter fileWriter =
autoClose(fileWriter = new FileWriter("someOtherFile"));
BufferedWriter bufferedWriter =
autoClose(bufferedWriter = new BufferedWriter(fileWriter));
// ... do something with bufferedWriter
}
};
// .. other logic, maybe more AutoFileClosers
} catch (RuntimeException e) {
// report or log the exception
}
Используя этот подход, вам никогда не придется беспокоиться о try/catch / finally, чтобы снова закрывать файлы.
Если это слишком тяжело для вашего использования, по крайней мере подумайте о том, чтобы придерживаться подхода try/catch и подхода "ожидающих" переменных, который он использует.
При закрытии связанных потоков вам нужно только закрыть самый внешний поток. Любые ошибки будут распространяться по цепочке и будут пойманы.
Обратитесь к Java I/O Streams для деталей.
Для решения проблемы
Однако, если flush() по какой-то причине вызывает исключение во время выполнения, out.close() никогда не будет вызываться.
Это не правильно. После того, как вы поймаете и проигнорируете это исключение, выполнение вернется после блока catch иout.close()
заявление будет выполнено.
Ваш коллега делает хорошее замечание по поводу исключения Runtime. Если вам абсолютно необходимо закрыть поток, вы всегда можете попробовать закрыть каждый из них по отдельности, снаружи, останавливаясь на первом исключении.
В эпоху Java 7 попробовать с ресурсами, безусловно, путь. Как упоминалось в нескольких предыдущих ответах, запрос на закрытие распространяется от самого внешнего потока к самому внутреннему потоку. Так что единственное закрытие - это все, что требуется.
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(f))) {
// do something with ois
}
Однако есть проблема с этим шаблоном. Try-with-resources не знает о внутреннем FileInputStream, поэтому, если конструктор ObjectInputStream выдает исключение, FileInputStream никогда не закрывается (пока сборщик мусора не доберется до него). Решение...
try (FileInputStream fis = new FileInputStream(f); ObjectInputStream ois = new ObjectInputStream(fis)) {
// do something with ois
}
Это не так элегантно, но более надежно. Будет ли это на самом деле проблемой, будет зависеть от того, какие исключения могут быть сгенерированы при создании внешнего объекта (объектов). ObjectInputStream может генерировать IOException, который вполне может обрабатываться приложением без завершения. Многие потоковые классы генерируют только непроверенные исключения, что может привести к завершению работы приложения.
Хорошей практикой является использование Apache Commons для обработки объектов, связанных с вводом-выводом.
в finally
использование пункта IOUtils
IOUtils.closeQuietly (bWriter); IOUtils.closeQuietly (oWritter);
Фрагмент кода ниже.
BufferedWriter bWriter = null;
OutputStreamWriter oWritter = null;
try {
oWritter = new OutputStreamWriter( httpConnection.getOutputStream(), "utf-8" );
bWriter = new BufferedWriter( oWritter );
bWriter.write( xml );
}
finally {
IOUtils.closeQuietly(bWriter);
IOUtils.closeQuietly(oWritter);
}
Коллега поднимает интересный вопрос, и есть основания для споров в любом случае.
Лично я бы проигнорировал RuntimeException
, потому что непроверенное исключение означает ошибку в программе. Если программа неверна, исправьте ее. Вы не можете "обработать" плохую программу во время выполнения.
Это удивительно неловкий вопрос. (Даже если предположить, acquire; try { use; } finally { release; }
код правильный.)
Если конструкция декоратора терпит неудачу, вы не будете закрывать основной поток. Поэтому вам необходимо явно закрыть базовый поток, будь то в конечном счете после использования или, что еще сложнее, после успешной передачи ресурса декоратору).
Если исключение вызывает сбой выполнения, вы действительно хотите сбросить?
Некоторые декораторы сами имеют ресурсы. Текущая реализация Sun ZipInputStream
например, выделена не куча памяти Java.
Утверждалось, что (IIRC) две трети ресурсов, используемых в библиотеке Java, реализованы явно некорректным образом.
в то время как BufferedOutputStream
закрывается даже на IOException
от flush
, BufferedWriter
закрывается правильно.
Мой совет: закройте ресурсы как можно более прямо и не позволяйте им портить другой код. OTOH, вы можете потратить слишком много времени на эту проблему - если OutOfMemoryError
это хорошо, но другие аспекты вашей программы, вероятно, имеют более высокий приоритет, и библиотечный код, вероятно, в любом случае нарушается в этой ситуации. Но я бы всегда писал:
final FileOutputStream rawOut = new FileOutputStream(file);
try {
OutputStream out = new BufferedOutputStream(rawOut);
... write stuff out ...
out.flush();
} finally {
rawOut.close();
}
(Смотри: не поймай!)
И, возможно, использовать идиому "Выполнить вокруг".
Java SE 7 try-with-resources не упоминается. Это избавляет от необходимости явно делать закрытие полностью, и мне очень нравится идея.
К сожалению, для разработки под Android эта конфета становится доступной только при использовании Android Studio (я думаю) и ориентации на Kitkat и выше.
Также вам не нужно закрывать все вложенные потоки.
проверить это http://ckarthik17.blogspot.com/2011/02/closing-nested-streams.html
Я использую, чтобы закрыть потоки, как это, не вкладывая try-catch в блоки finally
public class StreamTest {
public static void main(String[] args) {
FileOutputStream fos = null;
BufferedOutputStream bos = null;
ObjectOutputStream oos = null;
try {
fos = new FileOutputStream(new File("..."));
bos = new BufferedOutputStream(fos);
oos = new ObjectOutputStream(bos);
}
catch (Exception e) {
}
finally {
Stream.close(oos,bos,fos);
}
}
}
class Stream {
public static void close(AutoCloseable... array) {
for (AutoCloseable c : array) {
try {c.close();}
catch (IOException e) {}
catch (Exception e) {}
}
}
}
JavaDocs от Sun включают RuntimeException
s в их документации, как показано методом read(byte[], int, int) InputStream; задокументировано как выбрасывание исключений NullPointerException и IndexOutOfBoundsException.
Flush () в FilterOutputStream задокументирован только как выбрасывающий IOException, поэтому он на самом деле не выбрасывает RuntimeException
s. Любое, что может быть брошено, скорее всего, будет завернуто в IIOException
,
Это все еще может бросить Error
, но вы мало что можете с этим поделать; Sun рекомендует не пытаться их поймать.