代码之家  ›  专栏  ›  技术社区  ›  paercebal

在Java中…资源处理总是那么丑陋吗?

  •  15
  • paercebal  · 技术社区  · 16 年前

    我只是玩Java文件系统API,然后用下面的函数来复制二进制文件。原始源来自Web,但我添加了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子句中,因为第一个 关闭() 很好地扔,然后,第二个就不会被执行了。

    我知道C有 处置 可以用 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中生成丑陋和臃肿的代码来处理异常。 关闭() 缓冲流的方法?

    (请告诉我哪里错了……)

    编辑:是我吗,还是在更新此页面时,我看到问题和所有答案在几分钟内都减少了一点?有人在匿名还押的时候太享受自己了吗?

    编辑2: 麦克道威尔 提供了一个非常有趣的链接,我觉得我必须在这里提到: http://illegalargumentexception.blogspot.com/2008/10/java-how-not-to-make-mess-of-stream.html

    编辑3:在麦克道威尔的链接之后,我提出了一个Java 7的建议,该模式类似于使用C模式的模式: http://tech.puredanger.com/java7/#resourceblock . 我的问题被明确地描述了。显然,即使使用Java 7 do 问题依然存在。

    5 回复  |  直到 13 年前
        1
  •  18
  •   McDowell rahul gupta    13 年前

    在Java 6和更低的大多数情况下,尝试/最终模式是正确处理流的方法。

    一些人主张静默地关闭溪流。为此,请务必小心: Java: how not to make a mess of stream handling


    Java 7介绍 尝试使用资源 :

    /** 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()) {}
      }
    }
    
        2
  •  4
  •   Tom Hawtin - tackline    16 年前

    有一些问题,但是你在网上发现的代码确实很糟糕。

    关闭缓冲流将关闭下面的流。你真的不想那样做。您只需要刷新输出流。另外,没有必要为文件指定底层流。性能很糟糕,因为您一次只复制一个字节(实际上,如果您使用java.io,则可以使用transferto/transfertfrom,后者速度更快)。当我们讨论它时,变量名很糟糕。所以:

    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 作为一元后缀运算符关闭封闭块的末尾。

        3
  •  3
  •   Dale    16 年前

    是的,Java就是这样工作的。有控制反转-对象的用户必须知道如何清理对象,而不是在对象自身清理之后清理对象本身。不幸的是,大量的清理代码散布在Java代码中。

    C有“using”关键字在对象超出范围时自动调用Dispose。Java没有这样的东西。

        4
  •  3
  •   volley    16 年前

    不幸的是,这种类型的代码在Java中有点臃肿。

    顺便说一下,如果对osbuffer.read或odbuffer.write的某个调用引发了异常,那么您可能希望让该异常渗透到调用层次结构中。

    在finally子句中调用close()会导致原始异常被close()-调用生成的异常替换。换句话说,失败的close()-方法可能隐藏由read()或write()生成的原始异常。所以,我认为如果 只有当 其他方法没有抛出。

    我通常通过在内部尝试中包含一个明确的结束调用来解决这个问题:

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

    最后,为了提高性能,代码应该可以使用缓冲区(每次读/写多个字节)。不能用数字来支持这一点,但是更少的调用应该比在上面添加缓冲流更有效。

        5
  •  2
  •   Dónal    16 年前

    对于常见的IO任务(如复制文件),如上面所示的代码将重新生成控制盘。不幸的是,JDK没有提供任何更高级别的实用程序,但是ApacheCommonsIO提供。

    例如, FileUtils 包含用于处理文件和目录(包括复制)的各种实用程序方法。另一方面,如果您确实需要在JDK中使用IO支持, IOUtils 包含一组closequiely()方法,这些方法关闭读卡器、编写器、流等,而不引发异常。

    推荐文章