Java资源关闭

时间:2010-06-15 11:01:22

标签: java url exception-handling inputstream

我正在编写一个连接到网站并从中读取一行的应用。我是这样做的:

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。我应该从connection.getInputStream创建一个独立的InputStreamReader,还是从独立的InputStreamReader创建一个BufferedReader,而不是关闭所有两个读者? 我认为最好将结束方法放在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 个答案:

答案 0 :(得分:9)

Java中资源获取和发布的一般习惯是:

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

注意:

  • try应立即关注获取。这意味着你不能将它包装在装饰器中并保持安全(除去空格或将东西放在一行上也没有帮助:)。
  • finally一个版本,否则不会是例外安全。
  • 避免null,请使用final。否则你将会有杂乱的代码和NPE的潜力。
  • 通常不需要关闭装饰器,除非它有与之相关的其他资源。但是,您通常需要刷新输出,但在异常情况下请避免使用。
  • 异常应该传递给调用者,或者从周围的try块中捕获(Java会让你误入歧途)。

你可以用Execute Around idiom来抽象这个废话,所以你不必重复自己(只是写了很多样板)。

答案 1 :(得分:3)

关闭BufferedReader就足够了 - 这也会关闭底层读者。

Yishai posted a nice pattern用于关闭流(关闭可能会抛出另一个异常)。

答案 2 :(得分:3)

  

好吗?我的意思是,我在最后一行关闭了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 :(得分:3)

BufferedReader br = null;

您正在声明一个变量而不指定它(null不计算 - 在这种情况下它是无用的赋值)。这是Java中的代码“气味”(参考Effective Java; Code Complete,有关变量声明的更多信息)。

}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
  • 包围上述所有方法

前段时间,我花了一些时间思考如何avoid leaking resources/data when things go wrong

答案 4 :(得分:1)

  

我认为放置它会更好   关闭finally块中的方法

是的,永远。因为可能发生异常并且资源未正确释放/关闭。

您只需要关闭最外层的阅读器,因为它将负责关闭任何封闭的阅读器。

是的,这很丑......现在。我认为Java中有automatic resource management的计划。

答案 5 :(得分:1)

我正在使用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);
    }

}

答案 6 :(得分:0)

对于java.io中的任何嵌套流和读取器,您不需要多个close语句。很难在一个单独的事件中关闭多个东西 - 大多数构造函数都可以抛出异常,因此您将尝试关闭尚未创建的东西。

如果要关闭流,无论读取是否成功,那么您需要输入到finally。

不要为变量赋值null,然后比较它们以查看先前是否发生过某些事情;而是构建您的程序,以便只有在不抛出异常时才能到达关闭流的路径。除了用于迭代for循环的变量之外,变量不需要改变值 - 我倾向于将所有内容标记为final,除非有其他要求。在你的程序周围标记,告诉你如何处理当前正在执行的代码,然后根据这些标志改变行为,这是一种程序性(甚至是结构化的)编程风格。

如何嵌套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();
    }
}

正如McDowell指出的那样,如果new InputStreamReader抛出,您可能需要关闭输入流。

答案 7 :(得分:0)

在Java 8的范围内,我将使用类似的方法:

try(Resource resource = acquire()) {
    use(resource);
    reuse(resource);
}