Копирование файлов с файловыми блокировками в 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()). Однако это должно быть вызвано после снятия блокировки, что может вызвать проблемы, если один и тот же процесс выполняется более одного раза одновременно.

Я также рассматривал варианты блокировки исходного файла, но это не помогает, так как мне пришлось бы поместить его в FileInputStream, а я хочу передать File в FileUtils.copyFile(src, dest).

So:

  1. Есть ли более простой способ добиться того, что я пытаюсь сделать?
  2. Можно ли поставить блокировку на файл, а не на производную от файла?
  3. What would be the best way of resolving this?
    • i.e. do I just need to write my own approach method for part of this? (probably copyFile(...))

person amaidment    schedule 09.12.2013    source источник


Ответы (2)


Вы можете использовать Guava Google Core Library для сравнения двух файлов.

Как источник байтов

Документ ByteSource

        ByteSource inByte = Resources.asByteSource(srcFileURL);
        ByteSource outByte = Files.asByteSource(srcFileURL2);
        boolean fileEquals= inByte.contentEquals(outByte));
person Makky    schedule 09.12.2013
comment
Это хорошая идея, но как это работает с блокировкой файлов? Я не могу получить FileLock на ByteSource, я не могу создать ByteSource из файла, пока есть отдельная блокировка, и не имеет смысла делать ByteSource вне блокировки - т.е. с точки зрения проблемы это функционально эквивалентно используя apache.commons.io.FileUtils - person amaidment; 09.12.2013
comment
@amaidment вы можете получить URL-адрес из файла docs.oracle.com/javase/7/docs/api/java/io/File.html#toURL%28%29 . После того, как вы заблокируете файл, получите URL-адрес и получите источник входного байта и то же самое для файла назначения. - person Makky; 09.12.2013
comment
Нет, это не работает. Когда код вызывает inByte.contentEquals(outByte), он пытается получить доступ к заблокированному файлу и завершается с ошибкой IOException: процесс не может получить доступ к файлу, поскольку другой процесс заблокировал часть файла. - person amaidment; 09.12.2013
comment
Кроме того, как правило, не рекомендуется рекомендовать людям использовать устаревшие методы. - person amaidment; 09.12.2013

Итак... в конце концов, я решил написать свои собственные методы copy() и compare() для использования заблокированных объектов FileChannel. Ниже приведено решение, которое я придумал, хотя я ожидаю, что другие смогут предложить улучшения. Об этом стало известно, просмотрев исходный код классов apache.commons.io FileUtils и IOUtils.

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);
  }
}
person amaidment    schedule 10.12.2013