关于url:Java资源关闭

Java resource closing

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

1
2
3
4
5
6
7
8
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,而不是关闭所有两个阅读器?
我认为最好将close方法放置在finally块中,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
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();
}

但这很丑陋,因为关闭方法会引发异常,所以我必须处理或引发异常。

哪种解决方案更好? 或最佳解决方案是什么?


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

1
2
3
4
5
6
final Resource resource = acquire();
try {
    use(resource);
} finally {
    resource.release();
}

注意:

  • try应该立即跟随获取。这意味着您不能将其包装在装饰器中并保持安全性(删除空格或将内容放在一行上都无济于事:)。
  • 每个finally发行一个版本,否则将不是异常安全的。
  • 避免使用null,请使用final。否则,您将拥有凌乱的代码和潜在的NPE。
  • 通常,除非装饰器具有与之关联的其他资源,否则无需将其关闭。但是,通常需要刷新输出,但在例外情况下应避免这种情况。
  • 异常应该传递给调用者,或者从周围的try块中捕获(Java会使您误入歧途)。

您可以使用Execute Around惯用语来抽象这种废话,因此您不必重复自己(只需写很多样板)。


1
BufferedReader br = null;

您在声明变量时未分配变量(null不计数-在这种情况下,这是无用的分配)。这是Java中的代码"气味"(有关变量声明的更多信息,请参见有效Java;代码完成)。

1
2
3
4
}finally{
    br.close();
    isr.close();
}

首先,您只需要关闭最顶层的流装饰器(br将关闭isr)。其次,如果br.close()引发异常,则不会调用isr.close(),因此这不是声音代码。在某些异常情况下,您的代码将使用NullPointerException隐藏原始异常。

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

如果InputStreamReader构造函数引发(肯定不太可能)事件引发任何类型的运行时异常,则不会关闭来自连接的流。

利用Closeable接口减少冗余。

这是我如何编写您的代码:

1
2
3
4
5
6
7
8
9
10
11
12
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中

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


Is it good? I mean, I close the BufferedReader in the last line, but I do not close the InputStreamReader.

除了应该在finally中进行操作(这样即使在发生异常的情况下也可以确保关闭)的事实之外,也可以。 Java IO类使用装饰器模式。关闭将委派给基础流。

But it is ugly, because the closing methods can throw exception, so I have to handle or throw it.

当关闭引发异常时,通常仅意味着另一侧已关闭或删除,这完全超出了您的控制范围。您可以登录或忽略它。在一个简单的应用程序中,我将忽略它。可以肯定,在关键任务应用程序中,我会记录它。

简而言之,您的代码可以重写为:

1
2
3
4
5
6
7
8
9
10
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中,将进行自动资源处理,这将使您的代码简洁明了:

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

也可以看看:

  • Java IO教程
  • Java中的C#" using"关键字
  • 如何使用URLConnection

关闭BufferedReader就足够了-这也将关闭基础阅读器。

Yishai发布了一个很好的关闭流的模式(关闭可能会引发另一个异常)。


正如其他人建议的那样,我将为此使用apache commons IO,主要是IOUtils.toString(InputStream)和IOUtils.closeQuietly(InputStream):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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);
    }

}

I think it will be better to place the
closing methods in the finally block

是的,总是。因为可能会发生异常,并且资源没有正确释放/关闭。

您只需要关闭最外面的阅读器,因为它将负责关闭所有封闭的阅读器。

是的,这很丑。我认为有计划在Java中进行自动资源管理。


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

1
2
3
4
try(Resource resource = acquire()) {
    use(resource);
    reuse(resource);
}

对于java.io中的任何嵌套流和阅读器,您不需要多个close语句。最终需要关闭多个对象的情况很少见-大多数构造函数都可能引发异常,因此您将尝试关闭尚未创建的对象。

如果要关闭流,无论读取是否成功,则需要放入finally。

不要将null赋给变量,然后将它们进行比较以查看是否有更早的事情发生;而是构造程序,以便仅在未引发异常的情况下才能到达关闭流的路径。除了用于for循环的变量外,变量不需要更改值-除非有其他要求,否则我倾向于将所有内容标记为最终值。在程序周围具有标志来告诉您如何执行当前正在执行的代码,然后根据这些标志更改行为,这在很大程度上是一种程序性(甚至不是结构化的)编程风格。

嵌套try / catch / finally块的方式取决于您是否要以不同的方式处理不同阶段抛出的异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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()的结果,以确定网页的编码;您的代码仅使用平台的默认编码,这很可能是错误的。

尽管您的示例在结构上并不常见,因为它是非常程序化的。通常,您会在较大的系统中将打印和检索分开,并允许客户端代码处理任何异常(或有时捕获并创建自定义异常):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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抛出,则可能需要关闭输入流。