Должен ли я закрыть файловый канал?

#java #filechannel

#java #файловый канал

Вопрос:

Сегодня я столкнулся с проблемой с одним из наших служебных классов. Это помощник для файлов и содержит некоторые статические процедуры копирования файлов. Ниже приведены соответствующие методы, извлеченные вместе с методом тестирования.

Проблема в том, что иногда вызов setLastModified завершается неудачей, возвращая false .

На моем ПК (Windows 7, последняя версия Java) я иногда получаю сообщение «Ошибка setLastModified» (примерно 25 раз из 1000).

Я решил проблему прямо сейчас, удалив вызовы 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 s, а не FileChannel s, потому что исследования показывают, что закрытие FileChannel также закрывает Stream .

Комментарии:

1. Тестовый файл, который я использую, составляет около 4 мб.

2. Я заметил, что в вашем методе передачи, где вы используете потоки и каналы, у вас нет блока catch. Итак, я предполагаю, что если есть исключение IOException, вы достигаете блока finally и пытаетесь закрыть каналы. Там может быть другое исключение IOException, которое может быть вызвано вашим методом. Не отвечает на ваш вопрос, но я бы подумал, что было бы чище повторно создать исходное исключение и поместить блоки try catch с пустыми перехватами вокруг закрытия вашего канала.

3. Хорошая мысль, Крис, и хорошо подмечена. Я фактически сократил код, чтобы избавиться от ненужных отвлекающих факторов. На самом деле в реальном коде происходит немного больше обработки исключений.

4. Я получил такое же поведение — один раз. Это выглядело как своего рода перегрузка: несколько сбоев один за другим. Попробуйте использовать уникальные имена, без удаления / переименования. Возможно, Windows вмешивается в фоновую работу (сканер вирусов / индексация поисковой системой). Хотя это не обнадеживает.

5. @Joop У меня было такое же поведение — однажды это было с моим тестовым кодом выше или вы хотите сказать, что видели эту проблему раньше?

Ответ №1:

После некоторого исследования среди различных сайтов, на которых хранятся исходные файлы библиотеки Java, это очень похоже на то, что FileChannel.close в конечном итоге вызывает FileInputStream.close или FileOutputStream.close его родительский объект.

Это наводит меня на мысль, что вам следует либо закрыть файловый канал, либо поток, но не оба.

Ввиду этого я изменяю свой исходный пост, чтобы отразить один правильный метод, т. е. закрывать Stream s, а не Channel s.

Комментарии:

1. вы должны закрыть потоки, то есть то, что вы открыли. но да FileChannelImpl (если вы используете виртуальную машину Sun) вызывает родительский close() .

2. @bestess: Вы правы! Мои извинения — я действительно сделал это в коде, но забыл изменить свой пост здесь.

Ответ №2:

Если вы используете Java 7, вы могли бы использовать Files.copy(исходный путь, целевой путь, CopyOption … options) для этой операции, чтобы избежать написания, тестирования и отладки вашей собственной реализации.

В качестве альтернативы рассмотрите возможность использования внешней библиотеки, такой как Apache Commons IO. В частности, вы найдете FileUtils.CopyFile(файл srcFile, файл 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
  

Комментарии:

1. Спасибо matsev, и я, безусловно, перейду на Java7 как можно скорее. Странно то, что этот код долгое время находился в наших библиотеках утилит. Я обнаружил проблему только тогда, когда начал проверять возвращаемое значение setLastModified . Вы запустили код? Он сообщал об ошибках?

2. Нет, я не пробовал вашу реализацию. Мы используем Java 6 в нашем текущем проекте, и мы позволяем FileUtils обрабатывать наши файловые операции, а его родственные IOUtils — общие InputStream , OutputStream , Reader и Writer операции.

3. Это полезный совет, но вряд ли это ответ. Вы должны были оставить это в качестве комментария.