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

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

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

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

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

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

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


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

15 ответов

Решение

Смотрите также 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);
}

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

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

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

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

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

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

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

Он выдает IOException вместо возврата логического false в случае ошибки.

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

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

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

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

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

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

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

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

Следующий фрагмент кода НЕ является "альтернативой", но он надежно работает для меня как в 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();
          }
    }
}

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

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 являются полными путями к расположению файлов.

Ура кактус

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

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

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

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, ничего не делает, если существующий файл не существует, но, очевидно, может быть лучше приспособлен, чтобы это исправить.

Ну, я нашел довольно прямое решение этой проблемы -

      boolean retVal = targetFile.renameTo(new File("abcd.xyz"));
while(!retVal) {
    retVal= targetFile.renameTo(new File("abcd.xyz"));
}

Как предложил Аргеман, вы можете разместить счетчик и ограничить количество запусков цикла while, чтобы он не попадал в бесконечный цикл в случае, если какой-либо файл используется другим процессом Windows.

      int counter = 0;
boolean retVal = targetFile.renameTo(new File("abcd.xyz"));
while(!retVal && counter <= 10) {
        retVal = targetFile.renameTo(new File("abcd.xyz"));
        counter = counter + 1;
}

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

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

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

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

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

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

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

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

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

Это определяется в kernel32.dll.

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

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

Другие вопросы по тегам