Закрытие Java FileInputStream

Хорошо, я делал следующее (имена переменных изменены):


FileInputStream fis = null;
try
{
    fis = new FileInputStream(file);

    ... process ...

}
catch (IOException e)
{
    ... handle error ...
}
finally
{
    if (fis != null)
        fis.close();
}

Недавно я начал использовать FindBugs, что говорит о том, что я неправильно закрываю потоки. Я решаю посмотреть, можно ли что-нибудь сделать с блоком finally {}, а затем вижу: о да, close () может выбросить IOException. Что здесь делать людям? Библиотеки Java выдают слишком много проверенных исключений.


person Matt H    schedule 01.10.2008    source источник
comment
fis не может быть нулевым в момент, когда вы его тестируете. Он может быть нулевым в отсутствующем блоке finally, где вы должны его тестировать и закрывать. Но вопрос устарел с момента введения синтаксиса «попробуйте с ресурсами».   -  person user207421    schedule 28.04.2015
comment
Я соответствующим образом изменил код, чтобы людей не вводили в заблуждение.   -  person Matt H    schedule 28.04.2015


Ответы (9)


Для Java 7 и более поздних версий следует использовать try-with-resources. :

try (InputStream in = new FileInputStream(file)) {
  // TODO: work
} catch (IOException e) {
  // TODO: handle error
}

Если вы застряли на Java 6 или ниже ...

Этот шаблон позволяет избежать использования null:

    try {
        InputStream in = new FileInputStream(file);
        try {
            // TODO: work
        } finally {
            in.close();
        }
    } catch (IOException e) {
        // TODO: error handling
    }

Подробнее о том, как эффективно бороться с закрытием, читайте в этом сообщении в блоге: Java: как не испортить обработку потоков. В нем больше примеров кода, больше глубины и устранены подводные камни, связанные с заключением close в блок catch.

person McDowell    schedule 01.10.2008

Что-то вроде следующего должно сделать это, зависит от вас, выбрасываете ли вы или проглатываете IOException при попытке закрыть поток.

FileInputStream fis = null;
try
{
    fis = new FileInputStream(file);

    ... process ...


}
catch (IOException e)
{
    ... blah blah blah ...
}
finally
{
    try
    {
        if (fis != null)
            fis.close();
    }
    catch (IOException e)
    {
    }
}
person Max Stewart    schedule 01.10.2008

Вы можете использовать try-with-resources добавлена ​​функция JDK7. Он был создан именно для решения таких задач.

static String readFirstLineFromFile(String path) throws IOException {
  try (BufferedReader br = new BufferedReader(new FileReader(path))) {
    return br.readLine();
  }
}

В документации говорится:

Оператор try-with-resources гарантирует, что каждый ресурс будет закрыт в конце оператора.

person Edwin Dalorzo    schedule 17.05.2012

Вы также можете использовать простой статический вспомогательный метод:

public static void closeQuietly(InputStream s) {
   if (null == s) {
      return;
   }
   try {
      s.close();
   } catch (IOException ioe) {
      //ignore exception
   }
}

и используйте это из вашего блока finally.

person squiddle    schedule 01.10.2008

Нечего добавить, кроме очень незначительного стилистического предложения. В этом случае применим канонический пример самодокументируемого кода - укажите описательное имя переменной для игнорируемого IOException, которое вы должны уловить close().

Итак, ответ Сквиддла выглядит следующим образом:

public static void closeQuietly(InputStream s) {
   try {
      s.close();
   } catch (IOException ignored) {
   }
}
person serg10    schedule 01.10.2008

В большинстве случаев я считаю, что лучше не перехватывать исключения ввода-вывода и просто использовать try-finally:

final InputStream is = ... // (assuming some construction that can't return null)
try {
    // process is
    ...
} finally {
    is.close();
}

За исключением FileNotFoundException, вы, как правило, не можете "обойти" IOException. Единственное, что остается сделать, это сообщить об ошибке, и вы, как правило, будете обрабатывать ее дальше по стеку вызовов, поэтому я считаю, что лучше распространить исключение.

Поскольку IOException является проверенным исключением, вам нужно будет объявить, что этот код (и любой из его клиентов) throws IOException. Это может быть слишком шумно, или вы можете не раскрывать детали реализации использования ввода-вывода. В этом случае вы можете обернуть весь блок обработчиком исключений, который помещает IOException в RuntimeException или абстрактный тип исключения.

Подробно: мне известно, что приведенный выше код принимает любое исключение из блока try, когда операция close в блоке finally создает IOException. Я не думаю, что это большая проблема: как правило, исключение из блока try будет тем же IOException, которое вызывает сбой close (т.е. довольно редко ввод-вывод работает нормально, а затем выходит из строя в момент закрытия) . Если это вызывает беспокойство, возможно, стоит потрудиться, чтобы "замолчать" закрытие.

person Bruno De Fraine    schedule 01.10.2008
comment
В случае входного потока это довольно редко, вплоть до того, что становится неизвестным. Выходной поток может выйти из строя на close() из-за подразумеваемого flush(), которое происходит с любым потоком, полученным из FilterInputStream. - person user207421; 28.04.2015

Следующее решение правильно генерирует исключение в случае сбоя закрытия, не скрывая возможное исключение перед закрытием.

try {
    InputStream in = new FileInputStream(file);
    try {
        // work
        in.close();
    } finally {
        Closeables.closeQuietly(in);
    }
} catch(IOException exc) {
    // kernel panic
}

Это работает, потому что повторный вызов закрытия не действует.

Это зависит от Closeables, но при желании можно написать собственный метод closeQuietly, как показано в squiddle (см. также serg10).

Сообщение об ошибке закрытия в общем случае важно, потому что закрытие может записать некоторые последние байты в поток, например из-за буферизации. Следовательно, ваш пользователь хочет знать, не удалось ли это, или вы, вероятно, захотите как-то действовать. Конечно, это может быть неверно в конкретном случае FileInputStream, я не знаю (но по уже упомянутым причинам я думаю, что лучше сообщить об ошибке закрытия, если она все равно произойдет).

Приведенный выше код немного сложно понять из-за структуры встроенных блоков try. Это можно было бы считать более понятным с двумя методами: один генерирует исключение IOException, а другой его улавливает. По крайней мере, я бы выбрал именно это.

private void work() throws IOException {
    InputStream in = new FileInputStream(file);
    try {
        // work
        in.close();
    } finally {
        Closeables.closeQuietly(in);
    }
}

public void workAndDealWithException() {
    try {
        work();
    } catch(IOException exc) {
        // kernel panic
    }
}

На основе http://illegalargumentexception.blogspot.com/2008/10/java-how-not-to-make-mess-of-stream.html (на который ссылается МакДауэлл).

person Olivier Cailloux    schedule 23.07.2011

Надеюсь, что когда-нибудь мы получим закрытие в Java, и тогда мы потеряем много многословия.

Так что вместо этого где-то в javaIO будет вспомогательный метод, который вы можете импортировать, он, вероятно, будет иметь интерфейс «Closable», а также блок. Внутри этого вспомогательного метода try {closable.close ()} catch (IOException ex) {// blah} определяется раз и навсегда, и тогда вы сможете написать

 Inputstream s = ....;
 withClosable(s) {
    //your code here
 }
person Lars Westergren    schedule 01.10.2008

Вас беспокоит в первую очередь получение чистого отчета от FindBugs или наличие работающего кода? Это не обязательно одно и то же. Ваш исходный код в порядке (хотя я бы избавился от избыточной проверки if (fis != null), поскольку в противном случае был бы брошен OutOfMemoryException). FileInputStream имеет метод финализатора, который закроет поток для вас в том маловероятном случае, когда вы действительно получите исключение IOException при обработке. Просто не стоит беспокоиться о том, чтобы делать ваш код более сложным, чтобы избежать крайне маловероятного сценария

  1. вы получаете исключение IOException и
  2. это происходит так часто, что вы начинаете сталкиваться с проблемами невыполненной работы финализатора.

Изменить: если вы получаете так много исключений IOExceptions, что у вас возникают проблемы с очередью финализатора, тогда у вас есть гораздо более крупная рыба, которую нужно жарить! Речь идет о понимании перспективы.

person Dave Griffiths    schedule 01.10.2008
comment
Опираться на финализаторы - очень плохой ход. Финализатор будет вызываться только тогда, когда объект потока будет обработан сборщиком мусора, и вы, вероятно, отключитесь от файловых дескрипторов задолго до того, как это произойдет. Никогда не полагайтесь на функции завершения. Я не могу этого особо подчеркнуть. - person skaffman; 01.10.2008
comment
Нет никакой гарантии, будет ли вообще запущен финализатор. - person kpirkkal; 03.10.2008
comment
Я знаю об опасности полагаться на финализаторы. Я просто хотел сказать, что получение исключений IOExceptions не является нормальным процессом, и в этом необычном случае, вероятно, не стоит слишком зацикливаться на закрытии файлового дескриптора на месте. Когда вы в последний раз видели несколько исключений IOExceptions в FileInputStream и считали ли вы это нормальным поведением, с которым программа должна иметь дело и продолжать работу? - person Dave Griffiths; 03.05.2013