Java资源关闭

9
我正在编写一个连接网站并从中读取一行的应用程序。我是这样做的:
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();
}

但是它很丑,因为关闭方法可能会抛出异常,所以我必须处理或者抛出它。

哪种解决方案更好呢?或者说什么是最好的解决方案呢?


你的代码在finally子句中存在一个小问题。由于InputStreamReaderBufferedReader构造函数可能会抛出异常,因此在finally子句中isrbr仍然可能是null。你应该将finally子句更改为: finally {if(br!=null) br.close(); if(isr!=null) isr.close();}。这还不正确,因为br.close()可能会抛出异常,并且在这种情况下isr将永远无法关闭。Andreas的答案似乎是我个人认为应该遵循的方式。 - Behrang
除非你知道为什么,否则永远不要捕获“Exception”。 - Mr_and_Mrs_D
8个回答

9
Java中资源获取和释放的通用习语是:
final Resource resource = acquire();
try {
    use(resource);
} finally {
    resource.release();
}

注意:
  • try应该紧跟着获取。这意味着您不能将其包装在装饰器中并保持安全性(删除空格或将事物放在一行上也无济于事)。
  • 每个finally只能释放一次,否则它将不是异常安全的。
  • 避免使用null,使用final。否则,您将拥有混乱的代码和潜在的NPE。
  • 通常情况下,除非装饰器还有进一步的资源关联,否则不需要关闭它。但是,通常需要刷新输出,但在异常情况下要避免这样做。
  • 异常应该传递给调用者,或从周围的try块中捕获(Java在这里引导您走上错误的道路)。

您可以使用执行环绕惯用语来抽象化此无聊的操作,因此您不必重复自己(只需编写大量样板文件即可)。


1
如何在不抛出IOException的情况下实现获取FileInputStream等内容的获取方法?必须将上面的代码片段封装在另一个 try { // your code snippet } catch (IOException ioe) { ... }块中,这样做会显得很混乱。此外,在 finally 块中关闭多个资源时,此模式也无法正常工作,因为第一个资源可能会抛出异常,而下一个资源则完全不会被关闭。 - Behrang
1
如果你有多个资源,你需要多个try-finally语句。并且异常应该被处理在外面,可能是在方法之外。 - Tom Hawtin - tackline

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

请注意:

  • 无论抛出何种异常(运行时或已检查的异常)或在何处,代码都不会泄漏流资源
  • 没有catch块;应将异常传递到可以对错误处理做出明智决策的代码位置;如果此方法是正确的位置,则应使用try/catch包围上述所有内容

前段时间,我花了一些时间思考如何在出现问题时避免资源/数据泄漏


@Bob - 虽然我知道与问题无关,但你不知道谁在复制这些内容 - 这段代码中没有适当的字符处理,即没有检查返回数据的内容类型/编码。 - McDowell

3

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

}

1
我认为将关闭方法放在finally块中会更好。
是的,总是这样。因为可能会发生异常,资源没有被正确释放/关闭。
您只需要关闭最外层的读取器,因为它将负责关闭任何封闭的读取器。
是的,现在看起来很丑陋。我认为Java有计划实现自动资源管理

0

在Java 8的范围内,我会使用类似以下的代码:

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

0

在java.io中,对于任何嵌套的流和读取器,您不需要多个关闭语句。在单个finally中关闭多个内容非常罕见 - 大多数构造函数都可能会抛出异常,因此您将尝试关闭尚未创建的内容。

如果您想无论读取是否成功都关闭流,则需要将其放入finally中。

不要将null分配给变量,然后将它们与之前发生的事情进行比较;相反,构建程序以使得只有在未抛出异常的情况下才能到达关闭流的路径。除了用于迭代for循环的变量之外,变量不应需要更改值 - 我倾向于将所有内容标记为final,除非有其他要求。在程序中使用标志来告诉您如何到达当前正在执行的代码,然后根据这些标志更改行为,这是一种非常过程化(甚至不是结构化)的编程风格。

如何嵌套try/catch/finally块取决于您是否想要处理由不同阶段引发的异常。

private static final String questionUrl = "https://dev59.com/r07Sa4cB1Zd3GeqP3nEw";

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发生异常,您可能需要关闭输入流。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接