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 |
您在声明变量时未分配变量(
1 2 3 4 | }finally{ br.close(); isr.close(); } |
首先,您只需要关闭最顶层的流装饰器(
1 |
如果
利用
这是我如何编写您的代码:
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.
除了应该在
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 } } |
尽管您的示例在结构上并不常见,因为它是非常程序化的。通常,您会在较大的系统中将打印和检索分开,并允许客户端代码处理任何异常(或有时捕获并创建自定义异常):
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指出的那样,如果