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

92

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

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

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

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

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

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


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

Йоник
источник
3
Вы можете подождать / использовать JDK 7, который имеет гораздо лучшую поддержку файловой системы.
akarnokd
@ kd304, на самом деле я не могу дождаться или использовать версию раннего доступа, но интересно знать, что что-то подобное уже в пути!
Jonik

Ответы:

52

См. Также 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);
}
Алан
источник
7
к сожалению, Java7 не всегда является ответом (например, 42)
wuppi
1
Даже на ubuntu, JDK7, мы столкнулись с этой проблемой при запуске кода на EC2 с хранилищем EBS. File.renameTo не удалось, и File.canWrite тоже.
saurabheights
Имейте в виду, что это так же ненадежно, как File # renameTo (). Когда он терпит неудачу, он просто дает более полезную ошибку. Единственный разумно надежный способ, который я нашел, - это скопировать файл с помощью Files # copy на новое имя, а затем удалить оригинал с помощью Files # delete (которое само удаление тоже может потерпеть неудачу по той же причине, по которой Files # move может не работать) .
июн
26

Для чего стоит еще несколько понятий:

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

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

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

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

Йоник
источник
2
На данный момент я принимаю свой ответ, поскольку он описывает, что помогло в нашем случае. Тем не менее, если кто-то предложит отличный ответ на более общую проблему с renameTo (), не стесняйтесь публиковать сообщения, и я буду счастлив пересмотреть принятый ответ.
Jonik
4
6,5 лет спустя я думаю, что пришло время принять ответ JDK 7 , тем более что многие люди считают его полезным. =)
Jonik
19

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

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

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

См. Также ответ big lep в этой другой ветке .

MykennaC
источник
2
Кроме того, похоже, что JDK 1.7 будет включать улучшенную поддержку ввода-вывода файловой системы. Проверьте java.nio.file.Path.moveTo (): java.sun.com/javase/7/docs/api/java/nio/file/Path.html
MykennaC
2
JDK 1.7 не имеет методаjava.nio.file.Path.moveTo()
Malte Schwerhoff
5

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

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

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

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

Wuppi
источник
1
Я сделал то же самое, но со спящим в цикле 100 мс.
Лоуренс Дол
4

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

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

Ура Кактус

Кактус
источник
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();
          }
    }
}
сумасшедшая лошадь
источник
2
Хм, этот код удаляет srcFile, даже если renameTo (или destFile.delete) завершается неудачно и метод генерирует исключение IOException; Не уверен, что это хорошая идея.
Jonik
1
@Jonik, Thanx, исправлен код, чтобы не удалять файл src при неудачном переименовании.
Crazy
Спасибо, что поделились этим исправлением моей проблемы с переименованием в Windows.
BillMan
3

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

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

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

Johnydep
источник
Супер, это намного лучше! Благодарность! :-)
gaffcz
2

Почему нет....

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

касса
источник
2

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

fis = new FileInputStream(originalFile);
..
..
..
fis.close();// <<<---- Fixed by adding this
originalFile.renameTo(newDesitnationForOriginalFile);
Тарака
источник
1

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

        try {
            String n = f.getAbsolutePath();
            **n = n.substring(0, n.lastIndexOf("\\"));**
            File dest = new File(**n**, newName);
            f.renameTo(dest);
        } catch (Exception ex) {
           ...
Маркус Беккер
источник
0

Я знаю, что это отстой, но альтернативой является создание сценария летучей мыши, который выводит что-то простое, например «УСПЕХ» или «ОШИБКА», вызывает его, ожидает его выполнения и затем проверяет его результаты.

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

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

Рави Валлау
источник
-2

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

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

Антон Гоголев
источник
Спасибо. Но поскольку robocopy не является библиотекой Java, вероятно, было бы не очень просто (связать ее и) использовать ее из моего кода Java ...
Джоник
-2

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

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

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

Слепой
источник
1
Я чувствую, что проблема обертывания этого в JNI больше, чем усилия, необходимые для обертывания robocopy в декораторе процесса.
Кевин Монтроуз
да, это цена, которую вы платите за абстракцию - и когда она протекает, утечка хорошая = D
Chii
Спасибо, я мог бы рассмотреть это, если это не станет слишком сложным. Я никогда не использовал JNI и не смог найти хороших примеров вызова функции ядра Windows на SO, поэтому я разместил этот вопрос: stackoverflow.com/questions/1000723/…
Jonik
Вы можете попробовать обычную оболочку JNI, например johannburkard.de/software/nativecall, поскольку это довольно простой вызов функции.
Питер Смит
-8
 File srcFile = new File(origFilename);
 File destFile = new File(newFilename);
 srcFile.renameTo(destFile);

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

Ильтаф Халид
источник
11
Бывают случаи, когда renameTo () не работает надежно; в этом весь смысл вопроса.
Jonik