用流复制文件

时间:2011-10-13 02:06:29

标签: java

以下示例显示如何使用流复制文件。

private void copyWithStreams(File aSourceFile, File aTargetFile, boolean aAppend) {
log("Copying files with streams.");
ensureTargetDirectoryExists(aTargetFile.getParentFile());
InputStream inStream = null;
OutputStream outStream = null;
try{
  try {
    byte[] bucket = new byte[32*1024];
    inStream = new BufferedInputStream(new FileInputStream(aSourceFile));
    outStream = new BufferedOutputStream(new FileOutputStream(aTargetFile, aAppend));
    int bytesRead = 0;
    while(bytesRead != -1){
      bytesRead = inStream.read(bucket); //-1, 0, or more
      if(bytesRead > 0){
        outStream.write(bucket, 0, bytesRead);
      }
    }
  }
  finally {
    if (inStream != null) inStream.close();
    if (outStream != null) outStream.close();
  }
}
catch (FileNotFoundException ex){
  log("File not found: " + ex);
}
catch (IOException ex){
  log(ex);
}
}

private void ensureTargetDirectoryExists(File aTargetDir){
if(!aTargetDir.exists()){
  aTargetDir.mkdirs();
}
}
private static void log(Object aThing){
  System.out.println(String.valueOf(aThing));
}

对于上面的代码片段,我对四点感到困惑:

1)Bucket被分配为byte [] bucket = new byte [32 * 1024];是否有任何选择尺寸的标准,例如32 * 1024

2)为什么必须“抓住”这里?是否有任何关于将写作程序包括在内的规则?

3)我也不太清楚这里“尝试”的用法。看来作者在这个程序中使用了嵌套的try。

3 个答案:

答案 0 :(得分:1)

  1. 我们假设您无法轻松地将整个文件放入内存中,因此我们一次选择一个小块,处理它,然后抓住下一个块。在这种情况下,我们使用32千字节。您可以使用更多或更少,但工作在(小)硬盘扇区大小(通常为4kb)的倍数将更有效,并导致更少的IO操作。

  2. 如果您正在抛出异常,或者调用抛出异常的方法,则必须处理它。您可以使用try{}catch{}(或finally{})语句块将其包围,或者您可以将异常向上抛出到任何称为此方法的方法。在这种情况下,您有一个try{}语句,因此您附带了catchfinally语句。

  3. 作者做了一些不必要的事情:他可以使用一个try{},然后在finally{}语句之后放置catch{}块。相反,内部try{}在执行try{}块后将异常传递给外部finally{}。在此之后,catch{}块被激活,具体取决于抛出的Exception类型。

答案 1 :(得分:1)

1)32 * 1024达到32千字节,我认为是格式化NTFS卷时的默认分配单元大小。通过匹配底层文件系统,可能会有一些性能提升,因为如果没有正确缓存,则不会重写磁盘扇区。缓冲区越大,操作越平滑。你不能在较小的设备上使用巨大的缓冲区(虽然手机的内存越来越多)

2)这里的捕获可能非常烦人。如果您要求复制文件并且原始文件不存在,那么您获得的只是一个日志,调用此方法的程序可能会认为它有效。此方法可以重新抛出异常,并且调用程序将处理未找到的文件。 IO问题也是如此。

3)最后这一点非常重要,因为无论发生什么情况,您都希望确保资源被关闭。将嵌套的try语句设置为在记录其他异常后关闭资源时,实际上没有运行时的好处。 (小点,如果在关闭inStream时抛出异常,则不会关闭outStream,并且会丢失任何其他异常)。程序员可能认为将接近的语句放在阅读和写作附近看起来更干净但是它们很可能被移到外部尝试

答案 2 :(得分:0)

1)没有定义字节数组大小的标准。在给定的代码中,它使用32千字节但你可以使用任何值。字节数组的大小和文件的大小决定了你读取文件的次数,较大的缓冲区会导致较少的读取调用,但如果你处理小文件则不需要。

2)每当你在Java中使用一个可以引发异常的方法时,你需要捕获该异常并对它做一些事情。您可以在代码中处理异常(通常将堆栈跟踪打印到调试目的)或抛出它,这意味着当有人使用您的代码时,需要捕获代码的异常。

3)他所做的是捕捉两个可能的例外,并指明出现哪一个。由于FileNotFoundException扩展IOException,他可以只使用一次尝试,只捕获IOException,他用这种方式编码以了解IOExceptionFileNotFoundException还是任何其他IOException。就个人而言,我不会像作者那样编写代码,也不容易阅读。

也许重写代码会让你更容易理解尝试和捕获:

        System.out.println("Copying files with streams.");
    InputStream inStream = null;
    OutputStream outStream = null;
    try {
        inStream = new BufferedInputStream(new FileInputStream(aSourceFile));
        outStream = new BufferedOutputStream(new FileOutputStream(aTargetFile, aAppend));
    } catch (FileNotFoundException ex){
        // TODO Handle FileNotFoundException
        ex.printStackTrace();
    }
    try {
        byte[] bucket = new byte[32*1024];
        int bytesRead = 0;
        while(bytesRead != -1){
          bytesRead = inStream.read(bucket); //-1, 0, or more
          outStream.write(bucket, 0, bytesRead);
        }
    } catch (IOException ex){
        // TODO Handle IOException
        ex.printStackTrace();
    } finally {
        try {
            if (inStream != null) inStream.close();
            if (outStream != null) outStream.close();
        } catch (IOException ex){
            // TODO Handle IOException
            ex.printStackTrace();
        }
    }

它做同样的事情。