【问题标题】:Copying files with file locks in Java在 Java 中使用文件锁复制文件
【发布时间】:2013-12-26 14:33:35
【问题描述】:

我正在开发一个 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())。但是,这必须在锁被释放后调用,如果同一个进程同时执行了多次,这可能会导致问题。

我也考虑过将锁锁定在源文件上的想法,但这无济于事,因为我必须将其锁定在 FileInputStream 上,并且我想将 File 传递给 @987654341 @。

所以:

  1. 有没有更简单的方法来实现我想要做的事情?
  2. 是否可以锁定文件而不是文件的派生项?
  3. 解决此问题的最佳方法是什么?
    • 即我只需要为此编写自己的方法吗? (可能是copyFile(...)

【问题讨论】:

    标签: java file-copying file-locking


    【解决方案1】:

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

    As Byte Source

    ByteSource Doc

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

    【讨论】:

    • 这是个好主意,但它如何与文件锁定一起工作?我无法在ByteSource 上获得FileLock,当有单独的锁时,我无法从文件创建 ByteSource,并且将ByteSource 置于锁之外是没有意义的 - 即就问题而言,这在功能上等同于使用apache.commons.io.FileUtils
    • @amaidment 您可以从文件 docs.oracle.com/javase/7/docs/api/java/io/File.html#toURL%28%29 中获取 URL。锁定文件后,获取 URL 并获取目标文件的输入字节源和相同
    • 不,这行不通。当代码调用inByte.contentEquals(outByte)时,它试图访问一个已被锁定的文件,并失败并出现“IOException:该进程无法访问该文件,因为另一个进程已锁定文件的一部分”
    • 此外,作为一般规则,建议人们使用已弃用的方法并不是一个好习惯。
    【解决方案2】:

    所以...最后,我选择编写自己的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);
      }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-01-11
      • 2011-01-14
      • 2015-10-18
      • 2014-12-10
      • 2012-10-23
      • 1970-01-01
      • 2010-10-03
      • 1970-01-01
      相关资源
      最近更新 更多