Правильный способ закрыть вложенные потоки и записи в Java

Примечание.. Этот вопрос и большинство его ответов датируются до выпуска Java 7. Java 7 предоставляет Автоматическое управление ресурсами для этого легко. Если вы используете Java 7 или более позднюю версию, вы должны перейти на ответ Росса Джонсона.


Что считается лучшим, наиболее полным способом закрытия вложенных потоков на Java? Например, рассмотрите настройку:

FileOutputStream fos = new FileOutputStream(...)
BufferedOS bos = new BufferedOS(fos);
ObjectOutputStream oos = new ObjectOutputStream(bos);

Я понимаю, что операция закрытия должна быть застрахована (возможно, с помощью предложения 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, которые справляются с этим в мельчайших подробностях?

Ответ 1

Я обычно делаю следующее. Во-первых, определите класс, основанный на шаблоне, для решения проблемы try/catch mess

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);
                }
            }
        }
    }
}

Обратите внимание на "ожидающее" исключение - это касается случая, когда исключение, созданное во время закрытия, замаскировало бы исключение, которое, возможно, действительно беспокоило бы.

Наконец, сначала пытается закрыть извне любой декорированный поток, поэтому, если у вас есть BufferedWriter, который обертывает FileWriter, мы сначала пытаемся закрыть BuffereredWriter, и если это не удается, все равно попытайтесь закрыть сам FileWriter. (Обратите внимание, что определение Closeable вызывает close(), чтобы игнорировать вызов, если поток уже закрыт)

Вы можете использовать вышеуказанный класс следующим образом:

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 и "ожидающий" переменный подход, который он использует.

Ответ 2

При закрытии потоков с цепочкой вам нужно только закрыть внешний поток. Любые ошибки будут распространяться по цепочке и быть пойманными.

Подробнее см. Java I/O Streams.

Чтобы устранить проблему

Однако, если flush() выдает исключение во время выполнения по какой-либо причине, то out.close() никогда не будет вызываться.

Это неправильно. После того, как вы поймаете и проигнорируете это исключение, выполнение выберет резервную копию после блока catch и будет выполняться оператор out.close().

Ваш коллега замечает замечание Runtime. Если вам абсолютно необходимо, чтобы поток был закрыт, вы всегда можете попытаться закрыть каждый из них отдельно, извне, останавливаясь при первом исключении.

Ответ 3

В эпоху Java 7 try-with-resources, безусловно, путь. Как упоминалось в нескольких предыдущих ответах, запрос запроса распространяется от самого внешнего потока до самого внутреннего потока. Таким образом, требуется только одно закрытие.

try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(f))) {
  // do something with ois
}

Однако существует проблема с этим шаблоном. Ресурсы try-in-resources не знают о внутреннем FileInputStream, поэтому, если конструктор ObjectInputStream генерирует исключение, FileInputStream никогда не закрывается (пока сборщик мусора не дойдет до него). Решение...

try (FileInputStream fis = new FileInputStream(f); ObjectInputStream ois = new ObjectInputStream(fis)) {
  // do something with ois
}

Это не так элегантно, но более надежно. Будет ли это на самом деле проблемой, будет зависеть от того, какие исключения могут быть выбрасываться при построении внешнего объекта (ов). ObjectInputStream может вызывать IOException, который может быть обработан приложением без прерывания. Во многих классах потока генерируются исключительные исключения, что вполне может привести к завершению работы приложения.

Ответ 4

Хорошая практика использования Apache Commons для обработки объектов, связанных с IO.

В разделе 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);
}

Ответ 5

Коллега вызывает интересный момент, и есть основания спорить в любом случае.

Лично я игнорировал бы RuntimeException, потому что неконтролируемое исключение означает ошибку в программе. Если программа неверна, исправьте ее. Вы не можете "обрабатывать" плохую программу во время выполнения.

Ответ 6

Это удивительно неудобный вопрос. (Даже если предполагается, что код acquire; try { use; } finally { release; } верен.)

Если конструкция декоратора не удалась, тогда вы не будете закрывать базовый поток. Поэтому вам нужно явно закрыть базовый поток, будь то в конце после использования или, более diifcult после успешной передачи ресурса декоратору).

Если исключение приводит к ошибке выполнения, действительно ли вы хотите сбросить флажок?

Некоторые декораторы действительно имеют ресурсы сами. Текущая реализация 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();
}

(Смотрите: не поймайте!)

И, возможно, используйте идиому Execute Around.

Ответ 8

Java SE 7 try-with-resources, похоже, не упоминается. Это устраняет необходимость явно закрывать полностью, и мне очень нравится идея.

К сожалению, для разработки Android эта сладость становится доступной только с помощью Android Studio (я думаю) и таргетинга Kitkat и выше.

Ответ 9

Я использую для закрытия потоков, подобных этому, без вложенности 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) {}
    }
  } 
}

Ответ 10

Sun JavaDocs включает RuntimeException в свою документацию, как показано в InputStream read (byte [], int, int); задокументировано как бросание NullPointerException и IndexOutOfBoundsException.

FilterOutputStream flush() задокументирован как бросание IOException, поэтому на самом деле он не бросает никаких RuntimeException s. Любой, который может быть выброшен, скорее всего, будет завернут в IIOException.

Он все равно мог бы выбросить Error, но вы не можете с этим поделать; Sun рекомендует вам не пытаться их поймать.