Надежная альтернатива File.renameTo() в Windows?

Java File.renameTo() является проблематичным, особенно в Windows, похоже. Как сообщает документация API,

Многие аспекты поведения этого метод по своей сути зависит от платформы: переименовать возможно, не сможет файл из одной файловой системы в другую, он может быть не атомарным, а может не удалось, если файл с конечный абстрактный путь уже существует. Возвращаемое значение всегда должно проверьте, чтобы операция переименования была успешной.

В моем случае, как часть процедуры обновления, мне нужно переместить (переименовать) каталог, который может содержать гигабайты данных (много подкаталогов и файлов разного размера). Перемещение всегда выполняется внутри одного раздела/диска, поэтому нет реальной необходимости физически перемещать все файлы на диске.

Не должно быть никаких блокировок файлов для содержимого каталога, который нужно перенести, но, тем не менее, renameTo() не выполняет свою работу и возвращает false. (Я просто догадываюсь, что, возможно, некоторые блокировки файлов истекают несколько произвольно в Windows.)

В настоящее время у меня есть метод резервного копирования, который использует копирование и удаление, но это отстой, потому что может потребоваться много времени, в зависимости от размера папки. Я также рассматриваю возможность простого документирования того факта, что пользователь может перемещать папку вручную, чтобы не дожидаться часа, возможно. Но правильный путь, очевидно, был бы чем-то автоматическим и быстрым.

Итак, мой вопрос: вы знаете альтернативный, надежный подход к быстрому переходу/переименованию с Java на Windows, либо с простым JDK, либо с какой-либо внешней библиотекой. Или, если вы знаете простой способ обнаружить и освободить любые блокировки файлов для данной папки и всего ее содержимого (возможно, тысячи отдельных файлов), это тоже будет хорошо.


Изменить. В этом конкретном случае кажется, что мы ушли, используя только renameTo(), принимая во внимание еще несколько вещей; см. этот ответ.

Ответ 1

См. также метод Files.move() в JDK 7.

Пример:

String fileName = "MyFile.txt";

try {
    Files.move(new File(fileName).toPath(), new File(fileName).toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING);
} catch (IOException ex) {
    Logger.getLogger(SomeClass.class.getName()).log(Level.SEVERE, null, ex);
}

Ответ 2

Для чего это стоит, некоторые дополнительные понятия:

  • В Windows, renameTo(), похоже, терпит неудачу, если целевой каталог существует, даже если он пуст. Это меня удивило, поскольку я пытался использовать Linux, где renameTo() преуспел, если цель существовала, если она была пустой.

    (Очевидно, я не должен был предполагать, что подобные вещи работают одинаково на разных платформах, это именно то, о чем предупреждает Javadoc.)

  • Если вы подозреваете, что могут быть некоторые затяжные блокировки файлов, они ждут немного раньше, чем может помочь move/rename. (В один момент в нашем установщике/обновлении мы добавили действие "сна" и неопределенный индикатор выполнения в течение примерно 10 секунд, потому что может быть служба, зависящая от некоторых файлов). Возможно, даже попробуйте простой механизм повтора, который пробует renameTo(), а затем ждет период (который может постепенно увеличиваться), пока операция не завершится или не будет достигнут некоторый тайм-аут.

В моем случае большинство проблем, по-видимому, были решены, принимая во внимание оба вышеизложенного, поэтому нам не нужно делать собственный вызов ядра или что-то в этом роде.

Ответ 3

Оригинальное сообщение запросило "альтернативный, надежный подход к быстрому переходу/переименованию с Java в Windows, либо с простым JDK, либо с помощью некоторой внешней библиотеки".

Еще один вариант, еще не упомянутый здесь, - v1.3.2 или новее apache.commons.io библиотека, которая включает FileUtils.moveFile().

Он выдает исключение IOException вместо того, чтобы возвращать логическое значение false при ошибке.

См. также большой lep ответ в этом другом потоке.

Ответ 4

Следующий фрагмент кода НЕ является "альтернативным", но надежно работает для меня как в среде Windows, так и в Linux:

public static void renameFile(String oldName, String newName) throws IOException {
    File srcFile = new File(oldName);
    boolean bSucceeded = false;
    try {
        File destFile = new File(newName);
        if (destFile.exists()) {
            if (!destFile.delete()) {
                throw new IOException(oldName + " was not successfully renamed to " + newName); 
            }
        }
        if (!srcFile.renameTo(destFile))        {
            throw new IOException(oldName + " was not successfully renamed to " + newName);
        } else {
                bSucceeded = true;
        }
    } finally {
          if (bSucceeded) {
                srcFile.delete();
          }
    }
}

Ответ 5

В моем случае это казалось мертвым объектом в моем собственном приложении, которое хранило дескриптор этого файла. Так что решение работало для меня:

for (int i = 0; i < 20; i++) {
    if (sourceFile.renameTo(backupFile))
        break;
    System.gc();
    Thread.yield();
}

Преимущество: это довольно быстро, так как нет Thread.sleep() с определенным временем жесткого кодирования.

Недостаток: этот предел 20 является некотором жестко запрограммированным номером. Во всех моих тестах я = 1 достаточно. Но, конечно, я оставил его в 20 лет.

Ответ 6

Я знаю, что это кажется немного взломанным, но для чего мне это нужно, кажется, что у буферизированных читателей и писателей нет проблем с созданием файлов.

void renameFiles(String oldName, String newName)
{
    String sCurrentLine = "";

    try
    {
        BufferedReader br = new BufferedReader(new FileReader(oldName));
        BufferedWriter bw = new BufferedWriter(new FileWriter(newName));

        while ((sCurrentLine = br.readLine()) != null)
        {
            bw.write(sCurrentLine);
            bw.newLine();
        }

        br.close();
        bw.close();

        File org = new File(oldName);
        org.delete();

    }
    catch (FileNotFoundException e)
    {
        e.printStackTrace();
    }
    catch (IOException e)
    {
        e.printStackTrace();
    }

}

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

Приветствия Kactus

Ответ 7

В Windows я использую Runtime.getRuntime().exec("cmd \\c "), а затем используйте функцию переименования командной строки для фактического переименования файлов. Это намного более гибко, например, если вы хотите переименовать расширение всех файлов txt в каталоге, чтобы просто записать это в выходной поток:

переименовать *.txt *.bak

Я знаю, что это нехорошее решение, но, по-видимому, он всегда работал у меня, намного лучше, чем поддержка встроенного Java.

Ответ 8

Почему бы и нет....

import com.sun.jna.Native;
import com.sun.jna.Library;

public class RenamerByJna {
    /* Requires jna.jar to be in your path */

    public interface Kernel32 extends Library {
        public boolean MoveFileA(String existingFileName, String newFileName);
    }

    public static void main(String[] args) {
        String path = "C:/yourchosenpath/";
        String existingFileName = path + "test.txt";
        String newFileName = path + "renamed.txt";

        Kernel32 kernel32 = (Kernel32) Native.loadLibrary("kernel32", Kernel32.class);
            kernel32.MoveFileA(existingFileName, newFileName);
        }
}

работает над nwindows 7, ничего не делает, если существующий файл не существует, но, очевидно, может быть лучше приспособлен для исправления этого.

Ответ 9

В моем случае ошибка была в пути родительского каталога. Возможно, ошибка, мне пришлось использовать подстроку, чтобы получить правильный путь.

        try {
            String n = f.getAbsolutePath();
            **n = n.substring(0, n.lastIndexOf("\\"));**
            File dest = new File(**n**, newName);
            f.renameTo(dest);
        } catch (Exception ex) {
           ...

Ответ 10

У меня была аналогичная проблема. Файл был скопирован скорее на Windows, но хорошо работал на Linux. Я исправил проблему, закрыв открытый файлInputStream перед вызовом renameTo(). Протестировано в Windows XP.

fis = new FileInputStream(originalFile);
..
..
..
fis.close();// <<<---- Fixed by adding this
originalFile.renameTo(newDesitnationForOriginalFile);

Ответ 11

Я знаю, что это отстой, но альтернативой является создание bat script, который выводит что-то простое, например "SUCCESS" или "ERROR", вызывать его, ждать его выполнения и затем проверять его результаты.

Runtime.getRuntime(). exec ( "cmd/c start test.bat" );

Эта тема может быть интересной. Проверьте также класс Process на том, как читать консольный вывод другого процесса.

Ответ 12

Вы можете попробовать robocopy. Это не совсем "переименование", но оно очень надежное.

Robocopy предназначен для надежного зеркалирования каталогов или дерева каталогов. Он имеет функции для обеспечения копирования всех атрибутов и свойств NTFS и включает дополнительный код перезапуска для сетевых подключений, подлежащих сбою.

Ответ 13

Чтобы переместить/переименовать файл, вы можете использовать эту функцию:

BOOL WINAPI MoveFile(
  __in  LPCTSTR lpExistingFileName,
  __in  LPCTSTR lpNewFileName
);

Он определен в файле kernel32.dll.

Ответ 14

 File srcFile = new File(origFilename);
 File destFile = new File(newFilename);
 srcFile.renameTo(destFile);

Вышеприведенный простой код. Я тестировал на окнах 7 и отлично работал.