简体   繁体   English

Java中的RAII ......资源处理总是那么难看?

[英]RAII in Java… is resource disposal always so ugly?

I just played with Java file system API, and came down with the following function, used to copy binary files. 我刚刚玩了Java文件系统API,并提供了以下函数,用于复制二进制文件。 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. 最初的源代码来自Web,但我添加了try / catch / finally子句,以确保在出现错误之前,在退出函数之前缓冲流将被关闭(因此,我的操作系统资源被释放)。

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. 据我所知,我不能把两个close()放在finally子句中因为第一个close()可以抛出,然后第二个不会被执行。

I know C# has the Dispose pattern that would have handled this with the using keyword. 我知道C#有Dispose模式,可以用using关键字处理它。

I even know better a C++ code would have been something like (using a Java-like API): 我甚至知道更好的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
}

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? 我缺少一些东西,或者我是否真的必须在Java中生成丑陋和臃肿的代码,只是为了处理缓冲流的close()方法中的异常?

(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 编辑2: McDowell提供了一个非常有趣的链接,我觉得我必须在这里提到: 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 . 编辑3:在麦克道尔的链接之后,我向Java 7提出了类似于C#模式的提议:使用模式: http//tech.puredanger.com/java7/#resourceblock My problem is explicitly described. 我明确地描述了我的问题。 Apparently, even with the Java 7 do , the problems remain. 显然,即使在Java 7的do ,仍然是问题。

The try/finally pattern is the correct way to handle streams in most cases for Java 6 and lower. 在大多数情况下,try / finally模式是处理Java 6及更低版本的流的正确方法。

Some are advocating silently closing streams. 有些人主张默默关闭流。 Be careful doing this for these reasons: Java: how not to make a mess of stream handling 由于以下原因,请小心这样做: Java:如何不弄乱流处理


Java 7 introduces try-with-resources : 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 types will be automatically closed: 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()) {}
  }
}

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). 性能很糟糕,因为你一次复制一个字节(实际上,如果你使用java.io使用可以使用transferTo / transferFrom,这仍然有点快)。 While we are about it, the variable names suck to. 虽然我们是关于它的,变量名称很糟糕。 So: 所以:

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);
    }
}

If you find yourself using try-finally a lot, then you can factor it out with the "execute around" idiom. 如果你发现自己经常尝试使用try-finally,那么你可以用“执行”这个习惯来解决它。

In my opinion: Java should have someway of closing resources at end of scope. 在我看来:Java应该在范围结束时关闭资源。 I suggest adding private as a unary postfix operator to close at the end of the enclosing block. 我建议添加private作为一元postfix运算符,以封闭在封闭块的末尾。

Unfortunately, this type of code tends to get a bit bloated in Java. 不幸的是,这种类型的代码往往在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. 顺便说一句,如果对oSBuffer.read或oDBuffer.write的调用之一抛出异常,那么您可能希望让该异常渗透到调用层次结构中。

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. 在finally子句中对close()进行无保护调用将导致原始异常被close() - call生成的异常替换。 In other words, a failing close()-method may hide the original exception produced by read() or write(). 换句话说,失败的close() - 方法可能会隐藏read()或write()产生的原始异常。 So, I think you want to ignore exceptions thrown by close() if and only if the other methods did not throw. 所以,我认为你想忽略close()引发的异常,当且仅当其他方法没有抛出时。

I usually solve this by including an explicit close-call, inside the inner try: 我通常通过在内部try中包含一个显式的close-call来解决这个问题:

try {
    while (...) {
      read...
      write...
    }
    oSBuffer.close(); // exception NOT ignored here
    oDBuffer.close(); // exception NOT ignored here
  } finally {
    silentClose(oSBuffer); // exception ignored here
    silentClose(oDBuffer); // exception ignored here
  }
static void silentClose(Closeable c)  {
    try {
      c.close();
    } catch (IOException ie) {
      // Ignored; caller must have this intention
    }
  }

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. 无法通过数字来支持,但更少的呼叫应该比在顶部添加缓冲流更有效。

Yes, that's how java works. 是的,这就是java的工作方式。 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. 遗憾的是,这会导致许多清理代码分散在您的Java代码中。

C# has the "using" keyword to automatically call Dispose when an object goes out of scope. 当对象超出范围时,C#具有“using”关键字以自动调用Dispose。 Java has no such thing. Java没有这样的东西。

For common IO tasks such as copying a file, code such as that shown above is reinventing the wheel. 对于常见的IO任务,例如复制文件,如上所示的代码正在重新发明轮子。 Unfortunately, the JDK doesn't provide any higher level utilities, but apache commons-io does. 不幸的是,JDK没有提供任何更高级别的实用程序,但apache commons-io确实如此。

For example, FileUtils contains various utility methods for working with files and directories (including copying). 例如, FileUtils包含用于处理文件和目录(包括复制)的各种实用程序方法。 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. 另一方面,如果您确实需要在JDK中使用IO支持, IOUtils包含一组closeQuietly()方法,这些方法关闭读取器,写入器,流等,而不会抛出异常。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM