Java 中的 RAII...资源处理总是那么丑陋吗?
我刚刚玩了一下Java文件系统API,并得到了以下函数,用于复制二进制文件。 原始来源来自网络,但我添加了 try/catch/finally 子句,以确保如果发生错误,缓冲区流将在退出该函数之前关闭(从而释放我的操作系统资源)。
我精简了函数以显示模式:
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( )
缓冲流的方法?
(请告诉我我在某个地方错了......)
编辑:是我,还是在更新此页面时,我看到问题和所有答案在几分钟内都减少了一个点? 是否有人在保持匿名的同时太享受自己了?
编辑 2:McDowell 提供了一个非常有趣的链接,我觉得我必须在这里提及: http://illegalargumentexception.blogspot .com/2008/10/java-how-not-to-make-mess-of-stream.html
编辑 3:按照 McDowell 的链接,我偶然发现了一个类似于 C# 模式的 Java 7 提案使用模式: http://tech.puredanger.com/java7/#resourceblock 。 我的问题已明确描述。 显然,即使使用 Java 7 do
,问题仍然存在。
I just played with Java file system API, and came down with the following function, used to copy binary files. The original source came from the Web, but I added try/catch/finally clauses to be sure that, should something wrong happen, the Buffer Streams would be closed (and thus, my OS ressources freed) before quiting the function.
I trimmed down the function to show the pattern:
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
}
}
As far as I understand it, I cannot put the two close()
in the finally clause because the first close()
could well throw, and then, the second would not be executed.
I know C# has the Dispose pattern that would have handled this with the using
keyword.
I even know better a C++ code would have been something like (using a Java-like 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
}
I am missing something, or do I really have to produce ugly and bloated code in Java just to handle exceptions in the close()
method of a Buffered Stream?
(Please, tell me I'm wrong somewhere...)
EDIT: Is it me, or when updating this page, I saw both the question and all the answers decreased by one point in a couple of minutes? Is someone enjoying himself too much while remaning anonymous?
EDIT 2: McDowell offered a very interesting link I felt I had to mention here:
http://illegalargumentexception.blogspot.com/2008/10/java-how-not-to-make-mess-of-stream.html
EDIT 3: Following McDowell's link, I tumbled upon a proposal for Java 7 of a pattern similar to the C# using pattern: http://tech.puredanger.com/java7/#resourceblock . My problem is explicitly described. Apparently, even with the Java 7 do
, the problems remain.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
在大多数情况下,对于 Java 6 及更低版本,try/finally 模式是处理流的正确方法。
有些人主张悄悄关闭流。 由于以下原因,请小心执行此操作: Java:如何不让流处理变得混乱
Java 7 引入了try-with-resources:
AutoCloseable
类型将自动关闭:The try/finally pattern is the correct way to handle streams in most cases for Java 6 and lower.
Some are advocating silently closing streams. Be careful doing this for these reasons: Java: how not to make a mess of stream handling
Java 7 introduces try-with-resources:
AutoCloseable
types will be automatically closed:虽然存在一些问题,但您在网上发现的代码确实很差。
关闭缓冲区流会关闭下面的流。 你真的不想这样做。 您要做的就是刷新输出流。 此外,指定底层流用于文件也是没有意义的。 性能很差,因为您一次复制一个字节(实际上,如果您使用 java.io,可以使用 TransferTo/transferFrom,这仍然更快一点)。 当我们谈论它时,变量名称很糟糕。 所以:
如果您发现自己经常使用 try-finally,那么您可以使用“execute around”习惯用法将其分解出来。
在我看来:Java 应该有某种方式在作用域结束时关闭资源。 我建议添加
private
作为一元后缀运算符以在封闭块的末尾关闭。There are issues, but the code you found lying about on the web is really poor.
Closing the buffer streams closes the stream underneath. You really don't want to do that. All you want to do is flush the output stream. Also there's no point in specifying the underlying streams are for files. Performance sucks because you are copying one byte at a time (actually if you use java.io use can use transferTo/transferFrom which is a bit faster still). While we are about it, the variable names suck to. So:
If you find yourself using try-finally a lot, then you can factor it out with the "execute around" idiom.
In my opinion: Java should have someway of closing resources at end of scope. I suggest adding
private
as a unary postfix operator to close at the end of the enclosing block.不幸的是,这种类型的代码在 Java 中往往会有点臃肿。
顺便说一句,如果对 oSBuffer.read 或 oDBuffer.write 的调用之一引发异常,那么您可能希望让该异常渗透到调用层次结构中。
在finally 子句中对close() 进行无保护的调用将导致原始异常被close() 调用产生的异常替换。 换句话说,失败的 close() 方法可能会隐藏 read() 或 write() 产生的原始异常。 因此,我认为如果并且仅其他方法没有抛出异常,您想忽略 close() 抛出的异常。
我通常通过在内部 try 中包含显式的关闭调用来解决此问题:
最后,为了性能,代码可能应该使用缓冲区(每次读/写多个字节)。 无法用数字来支持这一点,但更少的调用应该比在顶部添加缓冲流更有效。
Unfortunately, this type of code tends to get a bit bloated in Java.
By the way, if one of the calls to oSBuffer.read or oDBuffer.write throws an exception, then you probably want to let that exception permeate up the call hierarchy.
Having an unguarded call to close() inside a finally-clause will cause the original exception to be replaced by one produced by the close()-call. In other words, a failing close()-method may hide the original exception produced by read() or write(). So, I think you want to ignore exceptions thrown by close() if and only if the other methods did not throw.
I usually solve this by including an explicit close-call, inside the inner try:
Finally, for performance, the code should probably work with buffers (multiple bytes per read/write). Can't back that by numbers, but fewer calls should be more efficient than adding buffered streams on top.
是的,java就是这样工作的。 存在控制反转 - 对象的用户必须知道如何清理对象,而不是对象本身自行清理。 不幸的是,这会导致大量清理代码分散在您的 Java 代码中。
C# 具有“using”关键字,可以在对象超出范围时自动调用 Dispose。 Java没有这样的东西。
Yes, that's how java works. There is control inversion - the user of the object has to know how to clean up the object instead of the object itself cleaning up after itself. This unfortunately leads to a lot of cleanup code scattered throughout your java code.
C# has the "using" keyword to automatically call Dispose when an object goes out of scope. Java has no such thing.
对于复制文件等常见 IO 任务,如上所示的代码是在重新发明轮子。 不幸的是,JDK 不提供任何更高级别的实用程序,但 apache commons-io 提供。
例如, FileUtils 包含用于处理文件和目录(包括复制)的各种实用方法。 另一方面,如果您确实需要使用 JDK 中的 IO 支持,IOUtils 包含一组 closeQuietly() 方法,可以关闭 Readers、Writers、Streams 等,而不引发异常。
For common IO tasks such as copying a file, code such as that shown above is reinventing the wheel. Unfortunately, the JDK doesn't provide any higher level utilities, but apache commons-io does.
For example, FileUtils contains various utility methods for working with files and directories (including copying). On the other hand, if you really need to use the IO support in the JDK, IOUtils contains a set of closeQuietly() methods that close Readers, Writers, Streams, etc. without throwing exceptions.