在Java中复制带有文件锁的文件



我正在开发一个Java进程,该进程应该有效地(递归地)将文件/目录从源位置复制到目标位置。

要做到这一点,我想:

  • 创建锁
  • 如果目标文件不存在,请复制它
  • 否则,如果目标文件不同,请复制它
  • 否则它们是一样的,所以什么都不做
  • 松开锁

为了检查内容是否相等,我计划使用Apache Commons IO FileUtils方法contentsEqual(...)。为了进行复制,我计划使用Apache Commons IO FileUtils方法copyFile(...)

所以,我想到的代码是(这只是针对文件,目录是递归处理的,直到文件的方法):

private static void checkAndUpdateFile(File src, File dest) throws IOException {
  FileOutputStream out = new FileOutputStream(dest);
  FileChannel channel = out.getChannel();
  FileLock lock = channel.lock();
  if (!dest.exists()) {
    FileUtils.copyFile(src, out);
  } else if (!FileUtils.contentEquals(src, dest)) {
    FileUtils.copyFile(src, out);
  } 
  lock.release();
  channel.close();
  out.close();
}

这样可以锁定文件(很棒),并复制文件(超级)。

但是,无论何时复制文件,它都会将复制文件的上次修改时间戳设置为复制时间。这意味着对FileUtils.contentEquals(src, dest)的后续调用将继续返回false,因此文件将被重新复制。

我真正想要的类似于FileUtils.copyFile(src, dest, true),它保留了文件时间戳,并且通过调用FileUtils.contentEquals(src, dest)可以实现。这将要求锁定在File上,而不是FileOutputStream上,否则对FileUtils.copyFile(src, dest, true)的调用将失败并引发异常,因为文件已锁定。

或者,我考虑做FileUtils.copyFile(src, dest, true)方法所做的事情,即调用dest.setLastModified(src.lastModified())。然而,这必须在锁释放后调用,如果同一进程同时执行多次,这可能会导致问题。

我也考虑过在源文件上加锁的ides,但这没有帮助,因为我必须把它放在FileInputStream上,并且我想把File传递给FileUtils.copyFile(src, dest)

因此:

  1. 有什么更简单的方法可以实现我想要做的事情吗
  2. 是否可以锁定文件,而不是文件的派生文件
  3. 解决这个问题的最佳方法是什么?
    • 也就是说,我只需要为其中的一部分编写自己的方法吗?(可能是copyFile(...)

您可以使用Guava Google Core Library来比较两个文件。

作为字节源

ByteSource文档

        ByteSource inByte = Resources.asByteSource(srcFileURL);
        ByteSource outByte = Files.asByteSource(srcFileURL2);
        boolean fileEquals= inByte.contentEquals(outByte));

所以。。。最后,我采用了编写自己的copy()compare()方法来使用已锁定的FileChannel对象的方法。以下是我提出的解决方案——尽管我预计其他人可能会提出改进建议。这是通过查看apache.commons.io类FileUtilsIOUtils的源代码得到的。

private static final int s_eof = -1;
private static final int s_byteBuffer = 10240;
private static void checkAndUpdateFile(File src, File dest) throws IOException {
  FileInputStream in = new FileInputStream(src);
  FileChannel srcChannel = in.getChannel();
  FileChannel destChannel = null;
  FileLock destLock = null;
  try {
    if (!dest.exists()) {
      final RandomAccessFile destFile = new RandomAccessFile(dest, "rw");
      destChannel = destFile.getChannel();
      destLock = destChannel.lock();
      copyFileChannels(srcChannel, destChannel);
      dest.setLastModified(src.lastModified());
    } else {
      final RandomAccessFile destFile = new RandomAccessFile(dest, "rw");
      destChannel = destFile.getChannel();
      destLock = destChannel.lock();
      if (!compareFileChannels(srcChannel, destChannel)) {
        copyFileChannels(srcChannel, destChannel);
        dest.setLastModified(src.lastModified());
      }
    }
  } finally {
    if (destLock != null) {
      destLock.release();
    }
    if (destChannel != null) {
      destChannel.close();
    }
    srcChannel.close();
    in.close();
  }
}
protected static void copyFileChannels(FileChannel src, 
                                       FileChannel dest) throws IOException {
  final long size = src.size();
  for (long pos = 0; pos < size; ) {
    long count = 
      ((size - pos) > s_byteBuffer) ? s_byteBuffer : (size - pos);
    pos += dest.transferFrom(src, pos, count);
  }
}
protected static boolean compareFileChannels(FileChannel a, 
                                             FileChannel b) throws IOException {
  if (a.size() != b.size()) {
    return false;
  } else {
    final ByteBuffer aBuffer = ByteBuffer.allocate(s_byteBuffer);
    final ByteBuffer bBuffer = ByteBuffer.allocate(s_byteBuffer);
    for (int aCh = a.read(aBuffer); s_eof != aCh; ) {
      int bCh = b.read(bBuffer);
      if (aCh != bCh || aBuffer.compareTo(bBuffer) != 0) {
        return false;
      }
      aBuffer.clear();
      aCh = a.read(aBuffer);
      bBuffer.clear();
    }
    return s_eof == b.read(bBuffer);
  }
}

最新更新