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

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

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

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

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

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

9
задан Sean Patrick Floyd 15 June 2010 в 12:33
поделиться

7 ответов

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

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

Примечание:

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

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

9
ответ дан 4 December 2019 в 10:30
поделиться

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

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

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

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

Короче говоря, ваш код можно переписать так:

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
ответ дан 4 December 2019 в 10:30
поделиться

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

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

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

Да, это уродливо... пока что. Я думаю, есть планы по автоматическому управлению ресурсами в Java.

1
ответ дан 4 December 2019 в 10:30
поделиться

Я бы использовал для этого apache commons 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
ответ дан 4 December 2019 в 10:30
поделиться

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

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

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

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

private static final String questionUrl = "http://stackoverflow.com/questions/3044510/";

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 бросает.

0
ответ дан 4 December 2019 в 10:30
поделиться

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

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

3
ответ дан 4 December 2019 в 10:30
поделиться
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
ответ дан 4 December 2019 в 10:30
поделиться
Другие вопросы по тегам:

Похожие вопросы: