Java中的RAII ……资源处理总是那么难看?
我刚刚玩了Java文件系统API,并提供了以下函数,用于复制二进制文件。 最初的源代码来自Web,但我添加了try / catch / finally子句,以确保在退出函数之前,如果发生错误,将关闭缓冲区流(因此,我的操作系统资源被释放)。
我减少了function以显示模式:
public static void copyFile(FileOutputStream oDStream, FileInputStream oSStream) throw etc... { BufferedInputStream oSBuffer = new BufferedInputStream(oSStream, 4096); BufferedOutputStream oDBuffer = new BufferedOutputStream(oDStream, 4096); try { try { int c; while((c = oSBuffer.read()) != -1) // could throw a IOException { oDBuffer.write(c); // could throw a IOException } } finally { oDBuffer.close(); // could throw a IOException } } finally { oSBuffer.close(); // could throw a IOException } }
据我所知,我不能把两个close()
放在finally子句中,因为第一个close()
可以抛出,然后第二个不会被执行。
我知道C#有Dispose模式,可以using
关键字处理它。
我甚至知道更好的C ++代码(使用类似Java的API):
void copyFile(FileOutputStream & oDStream, FileInputStream & oSStream) { BufferedInputStream oSBuffer(oSStream, 4096); BufferedOutputStream oDBuffer(oDStream, 4096); int c; while((c = oSBuffer.read()) != -1) // could throw a IOException { oDBuffer.write(c); // could throw a IOException } // I don't care about resources, as RAII handle them for me }
我缺少一些东西,或者我是否真的必须在Java中生成丑陋和臃肿的代码,只是为了处理缓冲流的close()
方法中的exception?
(请告诉我,我错了…)
编辑:是我,还是在更新此页面时,我看到问题和所有答案在几分钟内减少了一分? 有人在享受匿名的同时享受太多自己?
编辑2: McDowell提供了一个非常有趣的链接,我觉得我必须在这里提到: http : //illegalargumentexception.blogspot.com/2008/10/java-how-not-to-make-mess-of-stream.html
编辑3:遵循McDowell的链接,我在Java 7上提出了类似于C#模式的提议:使用模式: http : //tech.puredanger.com/java7/#resourceblock 。 我明确地描述了我的问题。 显然,即使使用Java 7,问题仍然存在。
在大多数情况下,try / finally模式是处理Java 6及更低版本的流的正确方法。
有些人主张默默关闭流。 由于以下原因,请小心这样做: Java:如何不混淆流处理
Java 7引入了try-with-resources :
/** transcodes text file from one encoding to another */ public static void transcode(File source, Charset srcEncoding, File target, Charset tgtEncoding) throws IOException { try (InputStream in = new FileInputStream(source); Reader reader = new InputStreamReader(in, srcEncoding); OutputStream out = new FileOutputStream(target); Writer writer = new OutputStreamWriter(out, tgtEncoding)) { char[] buffer = new char[1024]; int r; while ((r = reader.read(buffer)) != -1) { writer.write(buffer, 0, r); } } }
AutoCloseable
类型将自动关闭:
public class Foo { public static void main(String[] args) { class CloseTest implements AutoCloseable { public void close() { System.out.println("Close"); } } try (CloseTest closeable = new CloseTest()) {} } }
有问题,但你在网上发现的代码真的很差。
关闭缓冲区流会关闭下面的流。 你真的不想这样做。 您要做的就是刷新输出流。 另外,指定基础流是用于文件也没有意义。 性能很糟糕,因为你一次复制一个字节(实际上,如果你使用java.io使用可以使用transferTo / transferFrom,这仍然有点快)。 虽然我们是关于它的,变量名称很糟糕。 所以:
public static void copy( InputStream in, OutputStream out ) throw IOException { byte[] buff = new byte[8192]; for (;;) { int len = in.read(buff); if (len == -1) { break; } out.write(buff, 0, len); } }
如果你发现自己经常使用try-finally,那么你可以用“执行周期”成语来解决它。
在我看来:Java应该在范围结束时关闭资源。 我建议添加private
作为一元postfix运算符,以封闭在封闭块的末尾。
是的,这就是java的工作方式。 有控制反转 – 对象的用户必须知道如何清理对象而不是对象本身清理后自己。 遗憾的是,这会导致许多清理代码分散在您的Java代码中。
当对象超出范围时,C#具有“using”关键字以自动调用Dispose。 Java没有这样的东西。
不幸的是,这种类型的代码往往在Java中有点臃肿。
顺便说一句,如果对oSBuffer.read或oDBuffer.write的调用之一抛出exception,那么您可能希望让该exception渗透到调用层次结构中。
在finally子句中对close()进行无保护调用将导致原始exception被close() – 调用生成的exception替换。 换句话说,失败的close() – 方法可能会隐藏read()或write()产生的原始exception。 所以,我认为你想忽略close()抛出的exception,当且仅当其他方法没有抛出时。
我通常通过在内部try中包含一个显式的close-call来解决这个问题:
尝试{ 而(...){ 读... 写... } oSBuffer.close(); //exception此处不予忽略 oDBuffer.close(); //exception此处不予忽略 } finally { silentClose(oSBuffer); //这里忽略exception silentClose(oDBuffer); //这里忽略exception }
static void silentClose(Closeable c){ 尝试{ c.close(); } catch(IOException ie){ //忽略; 来电者必须有这个意图 } }
最后,为了提高性能,代码应该可以使用缓冲区(每次读/写多个字节)。 无法通过数字来支持,但更少的呼叫应该比在顶部添加缓冲流更有效。
对于常见的IO任务,例如复制文件,如上所示的代码正在重新发明轮子。 不幸的是,JDK没有提供任何更高级别的实用程序,但apache commons-io确实如此。
例如, FileUtils包含用于处理文件和目录(包括复制)的各种实用程序方法。 另一方面,如果您确实需要在JDK中使用IO支持, IOUtils包含一组closeQuietly()方法,这些方法关闭读取器,写入器,流等,而不会抛出exception。