我正在编写一个连接到网站并从中读取一行的应用。我是这样做的:
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();
}
但它很难看,因为关闭方法可以抛出异常,所以我必须处理或抛出它。
哪种解决方案更好?或者什么是最好的解决方案?
答案 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();
}
请注意:
前段时间,我花了一些时间思考如何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);
}