Закрытие ресурса Java - PullRequest
       14

Закрытие ресурса Java

9 голосов
/ 15 июня 2010

Я пишу приложение, которое подключается к веб-сайту и читает одну строку из него. Я делаю это так:

try{
        URLConnection connection = new URL("www.example.com").openConnection();
        BufferedReader rd = new BufferedReader(new InputStreamReader(connection.getInputStream()));
        String response = rd.readLine();
        rd.close();
    }catch (Exception e) {
        //exception handling
    }

Это хорошо? Я имею в виду, я закрываю BufferedReader в последней строке, но я не закрываю InputStreamReader. Должен ли я создать автономный InputStreamReader из connection.getInputStream и BufferedReader из автономного InputStreamReader, чем закрыть все два читателя? Я думаю, что будет лучше поместить методы закрытия в блок finally следующим образом:

InputStreamReader isr = null;
BufferedReader br = null;
try{
    URLConnection connection = new URL("www.example.com").openConnection();
    isr = new InputStreamReader(connection.getInputStream());
    br = new BufferedReader(isr);
    String response = br.readLine();
}catch (Exception e) {
    //exception handling
}finally{
    br.close();
    isr.close();
}

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

Какое решение лучше? Или какое было бы лучшее решение?

Ответы [ 8 ]

9 голосов
/ 15 июня 2010

Общая идиома приобретения и выпуска ресурсов в Java:

final Resource resource = acquire();
try {
    use(resource);
} finally {
    resource.release();
}

Примечание:

  • try должно следовать сразу за приобретением.Это означает, что вы не можете обернуть его в декораторе и поддерживать безопасность (и удаление пробелов или размещение строк в одной строке не поможет:).
  • Один выпуск на finally, иначе это не будет безопасным для исключений.
  • Избегайте null, используйте final.В противном случае у вас будет грязный код и потенциал для NPE.
  • Обычно нет необходимости закрывать декоратор, если с ним не связан дополнительный ресурс.Однако, как правило, вам нужно очищать выходные данные, но избегайте этого в случае исключения.
  • Исключение следует либо передать вызывающей стороне, либо перехватить из окружающего блока try (Java вводит вас в заблуждение здесь).

Вы можете абстрагироваться от этой чепухи с помощью идиомы Execute Around , так что вам не нужно повторяться (просто напишите много шаблонов).

3 голосов
/ 15 июня 2010
BufferedReader br = null;

Вы объявляете переменную без ее присвоения (null не считается - в данном случае это бесполезное присвоение). Это «запах» кода в Java (см. Эффективная Java ; Код завершен , чтобы узнать больше об объявлении переменных).

}finally{
    br.close();
    isr.close();
}

Во-первых, вам нужно только закрыть самый верхний потоковый декоратор (br закроет isr). Во-вторых, если br.close() сгенерирует исключение, isr.close() не будет вызвано, так что это не звуковой код. При определенных условиях исключения ваш код будет скрывать исходное исключение с NullPointerException.

isr = new InputStreamReader(connection.getInputStream());

Если (предположительно маловероятное) событие, из-за которого конструктор InputStreamReader выдал какое-либо исключение времени выполнения, поток из соединения не будет закрыт.

Используйте интерфейс Closeable для уменьшения избыточности.

Вот как бы я написал ваш код:

URLConnection connection = new URL("www.example.com").openConnection();
InputStream in = connection.getInputStream();
Closeable resource = in;
try {
  InputStreamReader isr = new InputStreamReader(in);
  resource = isr;
  BufferedReader br = new BufferedReader(isr);
  resource = br;
  String response = br.readLine();
} finally {
  resource.close();
}

Обратите внимание:

  • независимо от того, какое исключение выдается (во время выполнения или проверено) или где, код не пропускает ресурсы потока
  • блок блокировки отсутствует; исключения должны быть переданы туда, где код может принять разумное решение об обработке ошибок; если бы этот метод был правильным, вы бы окружили все вышеперечисленное командой try / catch

Некоторое время назад я потратил некоторое время на размышления о том, как избежать утечки ресурсов / данных, когда что-то пойдет не так .

3 голосов
/ 15 июня 2010

Это хорошо?Я имею в виду, я закрываю BufferedReader в последней строке, но я не закрываю InputStreamReader.

Помимо того, что это должно быть сделано в finally (чтобы обеспечить закрытие даже в случае исключения), это нормально.Классы Java IO используют шаблон декоратора.Закрытие будет делегировано нижележащим потокам.

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

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

В ореховом коде ваш код можно переписать так:

BufferedReader br = null;
try {
    URLConnection connection = new URL("www.example.com").openConnection();
    br = new BufferedReader(new InputStreamReader(connection.getInputStream()));
    String response = br.readLine();
}catch (Exception e) {
    //exception handling
}finally{
    if (br != null) try { br.close(); } catch (IOException ignore) {}
}

В Java 7 будет автоматическая обработка ресурсовкоторый сделал бы ваш код таким кратким, как:

try (BufferedReader br = new InputStreamReader(new URL("www.example.com").openStream())) {
    String response = br.readLine();
} catch (Exception e) {
    //exception handling
}

См. также:

3 голосов
/ 15 июня 2010

Достаточно закрыть BufferedReader - это также закроет базовый читатель.

Ишай опубликовал хороший шаблон для закрытия потоков (закрытие может вызвать другое исключение).

1 голос
/ 15 июня 2010

Я бы использовал Apache IO для этого, как предлагали другие, в основном IOUtils.toString (InputStream) и IOUtils.closeQuietly (InputStream) :

public String readFromUrl(final String url) {

    InputStream stream = null; // keep this for finally block

    try {
        stream = new URL(url).openConnection().getInputStream();  // don't keep unused locals
        return IOUtils.toString(stream);
    } catch (final IOException e) {
        // handle IO errors here (probably not like this)
        throw new IllegalStateException("Can't read URL " + url, e);
    } finally {
        // close the stream here, if it's null, it will be ignored
        IOUtils.closeQuietly(stream);
    }

}
1 голос
/ 15 июня 2010

Я думаю, что будет лучше разместить методы закрытия в блоке finally

Да, всегда.Потому что может возникнуть исключение, и ресурсы не будут освобождены / закрыты должным образом.

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

0 голосов
/ 11 сентября 2018

В рамках Java 8 я бы использовал так:

try(Resource resource = acquire()) {
    use(resource);
    reuse(resource);
}
0 голосов
/ 15 июня 2010

Вам не нужно многократные операторы close для любого из вложенных потоков и читателей в java.io. В конце концов, очень редко нужно закрывать более одной вещи в одном - большинство конструкторов могут выдавать исключение, поэтому вы пытаетесь закрыть вещи, которые вы еще не создали.

Если вы хотите закрыть поток независимо от того, успешно ли выполнено чтение, вам нужно вставить в finally.

Не присваивайте переменным значение null, а затем сравнивайте их, чтобы увидеть, произошло ли что-то раньше; вместо этого структурируйте свою программу так, чтобы путь, по которому вы закрывали поток, мог быть достигнут только в том случае, если исключение не выдается. Помимо переменных, используемых для итерации для циклов, переменные не должны изменять значение - я склонен отмечать все как окончательное, если нет необходимости делать иначе. Наличие флагов вокруг вашей программы, чтобы рассказать вам, как вы добрались до исполняемого в данный момент кода, а затем изменение поведения на основе этих флагов - это очень процедурный (даже не структурированный) стиль программирования.

То, как вы вкладываете блоки try / catch / finally, зависит от того, хотите ли вы по-разному обрабатывать исключения, выдаваемые различными этапами.

private static final String questionUrl = "/3095190/zakrytie-resursa-java";

public static void main ( String...args )
{
    try {
        final URLConnection connection = new URL ( args.length > 0 ? args[0] : questionUrl ).openConnection();

        final BufferedReader br = new BufferedReader ( new InputStreamReader (
                    connection.getInputStream(), getEncoding ( connection ) ) );

        try {
            final String response = br.readLine();

            System.out.println ( response );
        } catch ( IOException e ) {
            // exception handling for reading from reader
        } finally {
            // br is final and cannot be null. no need to check
            br.close();
        }
    } catch ( UnsupportedEncodingException  uee ) {
        // exception handling for unsupported character encoding
    } catch ( IOException e ) {
        // exception handling for connecting and opening reader
        // or for closing reader
    }
}

getEncoding необходимо проверить результаты соединений getContentEncoding() и getContentType(), чтобы определить кодировку веб-страницы; ваш код просто использует кодировку платформы по умолчанию, которая может быть неправильной.

Ваш пример, хотя и необычен в структурированном выражении, поскольку он очень процедурный; обычно вы должны разделить печать и получение в более крупной системе и позволить клиентскому коду обрабатывать любое исключение (или иногда перехватывать и создавать пользовательское исключение):

public static void main ( String...args )
{
    final GetOneLine getOneLine = new GetOneLine();

    try {
        final String value = getOneLine.retrieve ( new URL ( args.length > 0 ? args[0] : questionUrl ) );
        System.out.println ( value );
    } catch ( IOException e ) {
        // exception handling for retrieving one line of text
    }
}

public String retrieve ( URL url ) throws IOException
{
    final URLConnection connection = url.openConnection();
    final InputStream in = connection.getInputStream();

    try {
        final BufferedReader br = new BufferedReader ( new InputStreamReader (
                    in, getEncoding ( connection ) ) );

        try {
            return br.readLine();
        } finally {
            br.close();
        }
    } finally {
        in.close();
    }
}

Как указывал Макдауэлл, вам может потребоваться закрыть входной поток, если выбрасывает new InputStreamReader.

...