Надежная альтернатива File.renameTo () в Windows? - PullRequest
87 голосов
/ 16 июня 2009

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

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

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

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

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

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


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

Ответы [ 14 ]

45 голосов
/ 27 марта 2011

См. Также метод 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);
}
25 голосов
/ 17 июня 2009

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

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

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

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

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

19 голосов
/ 28 мая 2010

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

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

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

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

4 голосов
/ 02 января 2013

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

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

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

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

4 голосов
/ 17 февраля 2011

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

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

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

2 голосов
/ 11 января 2012

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

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

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

1 голос
/ 02 апреля 2014

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

fis = new FileInputStream(originalFile);
..
..
..
fis.close();// <<<---- Fixed by adding this
originalFile.renameTo(newDesitnationForOriginalFile);
1 голос
/ 17 ноября 2012

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

        try {
            String n = f.getAbsolutePath();
            **n = n.substring(0, n.lastIndexOf("\\"));**
            File dest = new File(**n**, newName);
            f.renameTo(dest);
        } catch (Exception ex) {
           ...
1 голос
/ 25 октября 2012

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

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

...