我应该关闭FileChannel吗?

13

我今天遇到了一个我们实用工具类的问题。它是文件的帮助程序,包含一些静态文件复制例程。以下是相关方法和测试方法的提取。

问题在于有时setLastModified调用会失败,并返回false。

在我的PC上(Windows 7,最新的Java),我有时会收到“setLastModified失败”的消息(每1000次约25次)。

目前我已通过删除FileChannel.close调用来解决了这个问题,但我更希望了解为什么会出现这种情况,即使那是正确的解决方案。

还有其他人遇到过同样的问题吗?

private void testCopy() throws FileNotFoundException, IOException {
  File src = new File("C:\\Public\\Test-Src.txt");
  File dst = new File("C:\\Public\\Test-Dst.txt");

  for (int i = 0; i < 1000; i++) {
    copyFile(src, dst);
  }
}

public static void copyFile(final File from, final File to) throws FileNotFoundException, IOException {
  final String tmpName = to.getAbsolutePath() + ".tmp";
  // Copy to a .tmp file.
  final File tmp = new File(tmpName);
  // Do the transfer.
  transfer(from, tmp);
  // Preserve time.
  if (!tmp.setLastModified(from.lastModified())) {
    System.err.println("setLastModified failed!");
  }
  // In case there's one there already.
  to.delete();
  // Rename it in.
  tmp.renameTo(to);
}

public static void transfer(final File from, final File to) throws IOException {
  FileInputStream in = null;
  FileOutputStream out = null;
  try {
    in = new FileInputStream(from);
    out = new FileOutputStream(to);
    transfer(in, out);
  } finally {
    if (null != in) {
      in.close();
    }
    if (null != out) {
      out.close();
    }
  }
}

public static void transfer(final FileInputStream from, final FileOutputStream to) throws IOException {
  FileChannel srcChannel = null;
  FileChannel dstChannel = null;
  //try {
    srcChannel = from.getChannel();
    dstChannel = to.getChannel();
    srcChannel.transferTo(0, srcChannel.size(), dstChannel);
  //} finally {
  //  if (null != dstChannel) {
  //    dstChannel.close();
  //  }
  //  if (null != srcChannel) {
  //    srcChannel.close();
  //  }
  }
}

编辑:我已将代码更改为仅关闭Streams而不是FileChannel,因为研究表明关闭FileChannel也会关闭Stream


我正在使用的测试文件大约有4MB。 - OldCurmudgeon
1
我注意到你在传输方法中使用流和通道时没有使用catch块。因此,我假设如果出现IOException,您将到达finally块并尝试关闭通道。可能会有另一个IOException,这可能是由您的方法抛出的异常。虽然这不是回答您的问题,但我认为重新抛出原始异常并在通道关闭周围放置try catch块会更清晰。 - Chris Aldrich
很好的观察,Chris。我实际上已经简化了代码以消除不必要的干扰。实际代码中还有更多的异常处理。 - OldCurmudgeon
我遇到了同样的问题-只出现了一次。看起来像是一种拥塞:一个接一个地出现多个失败。尝试使用唯一的名称,不要删除/重命名。可能是Windows在后台干扰了(病毒扫描器/搜索引擎索引)。虽然并不令人放心。 - Joop Eggen
@Joop 我有同样的问题-一旦出现。这是使用我在上面提供的测试代码还是你说你以前见过这个问题? - OldCurmudgeon
transferTo 在 Windows 上不受支持,即它是模拟的,这就是为什么它总是传输整个文件,在 Linux 上可能不会传输所有内容,因此需要循环。我唯一看到 setModified 失败的方式是文件被锁定。同时尝试使用唯一的文件名来排除任何并发代码。 - bestsss
2个回答

11
经过对多个Java库源代码的研究,看起来 FileChannel.close 最终会调用其父对象的 FileInputStream.close 或者 FileOutputStream.close 方法。
这提示我应该只关闭 文件通道或流,而不是同时关闭两者
鉴于此,我将更改原始帖子以反映正确的方法,即关闭 Stream 而不是 Channel

你应该关闭流,也就是你打开的。但是如果你使用Sun的VM,FileChannelImpl确实会调用父类的close()方法。 - bestsss
@bestess:你说得对!非常抱歉 - 我实际上在代码中已经做了这个更改,但忘记在这里修改我的帖子。 - OldCurmudgeon

3
如果您正在使用Java 7,您可以使用Files.copy(Path source, Path target, CopyOption... options)来完成此操作,以避免编写、测试和调试自己的实现。
或者,考虑使用外部库,如Apache Commons IO。具体而言,您会发现FileUtils.copyFile(File srcFile, File destFile)很有趣:
/** 
 * Copies a file to a new location preserving the file date.
 * [...]
 * @param srcFile  an existing file to copy, must not be <code>null</code>
 * @param destFile  the new file, must not be <code>null</code>
 * [...]
 */
public static void copyFile(File srcFile, File destFile) throws IOException

感谢matsev,我一定会尽快转移到Java7。奇怪的是,这段代码已经存在于我们的实用程序库中很长时间了。只有当我开始检查setLastModified的返回值时,才发现了这个问题。你运行过这段代码吗?它报告了错误吗? - OldCurmudgeon
不,我还没有尝试过你的实现。我们在当前项目中使用Java 6,并让FileUtils处理我们的文件操作,它的兄弟IOUtils处理通用的InputStreamOutputStreamReaderWriter操作。 - matsev
1
这是一个有用的建议,但并不完全是答案。你应该将它留作评论。 - Malcolm

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接