Надійний варіант File.renameTo () у Windows?


92

Java, File.renameTo()здається, проблематична, особливо в Windows. Як сказано в документації API ,

Багато аспектів поведінки цього методу за своєю суттю залежать від платформи: Операція перейменування може не змогти перемістити файл з однієї файлової системи в іншу, вона може бути не атомною і не матиме успіху, якщо файл із цільовим абстрактним іменем шляху вже існує. Повернене значення завжди слід перевіряти, щоб переконатись, що операція перейменування була успішною.

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

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

На даний момент у мене є резервний метод, який використовує копіювання та видалення, але це відмовно, оскільки це може зайняти багато часу, залежно від розміру папки. Я також розглядаю можливість просто задокументувати той факт, що користувач може переміщати папку вручну, щоб уникнути очікування годинами. Але "Правильний шлях", очевидно, буде чимось автоматичним і швидким.

Отже, моє запитання полягає в тому, чи знаєте ви альтернативний, надійний підхід для швидкого переміщення / перейменування за допомогою Java у Windows , як із простим JDK, так і з якоюсь зовнішньою бібліотекою. Або якщо ви знаєте простий спосіб виявити та звільнити будь-які блокування файлів для даної папки та всього її вмісту (можливо, тисячі окремих файлів), це теж було б добре.


Редагувати : У цьому конкретному випадку, здається, ми впоралися з використанням, просто renameTo()взявши до уваги ще кілька речей; див. цю відповідь .


3
Ви можете почекати / використовувати JDK 7, який має набагато кращу підтримку файлової системи.
akarnokd

@ kd304, насправді я не можу зачекати або скористатися версією раннього доступу, але цікаво дізнатися, що щось подібне вже на шляху!
Джонік,

Відповіді:


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.renameНе вдалося, як і File.canWrite.
saurabheights

Пам’ятайте, що це так само ненадійно, як File # renameTo (). Він просто дає більш корисну помилку, коли не вдається. Єдиний досить надійний спосіб, який я знайшов, - це скопіювати файл із файлом # скопіювати на нове ім'я, а потім видалити оригінал за допомогою файлу # видалити (що може видалити і саме видалення, з тієї ж причини, що може не вдатися переміщення файлів #) .
jwenting

26

Для чого варто, ще кілька понять:

  1. У Windows, renameTo()здається, не вдається, якщо цільовий каталог існує, навіть якщо він порожній. Це мене здивувало, оскільки я пробував на Linux, де це renameTo()вдалося, якщо ціль існувала, поки вона була порожньою.

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

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

У моєму випадку більшість проблем, здається, були вирішені, беручи до уваги обидва вищезазначені, тому, зрештою, нам не потрібно буде робити власний виклик ядра або щось подібне.


2
Наразі я приймаю власну відповідь, оскільки вона описує, що допомогло у нашому випадку. І все ж, якщо хтось запропонує чудову відповідь на більш загальну проблему з renameTo (), сміливо публікуйте повідомлення, і я із задоволенням перегляну прийняту відповідь.
Джонік

4
Через 6,5 років я вважаю, що настав час прийняти відповідь JDK 7 , тим більше, що багато людей вважають це корисним. =)
Йонік

19

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

Інший варіант, про який тут ще не згадується, - це v1.3.2 або пізніша версія бібліотеки apache.commons.io , що включає FileUtils.moveFile () .

Він видає IOException замість повернення логічного значення false при помилці.

Див. Також відповідь великого леп у цій іншій темі .


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()
Мальте Швергофф

5

У моєму випадку це здавалося мертвим об'єктом у моєму власному додатку, який зберігав дескриптор цього файлу. Отже, це рішення спрацювало для мене:

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

Перевага: це досить швидко, оскільки немає Thread.sleep () з певним строко закодованим часом.

Недолік: обмеження 20 - це якесь твердо закодоване число. У всіх моїх тестах достатньо i = 1. Але, щоб бути впевненим, я залишив це в 20.


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, якщо перейменування не вдається.
божевільний кінь

Дякуємо, що поділилися цим виправленням моєї проблеми з перейменуванням у Windows.
BillMan

3

У Windows я використовую, Runtime.getRuntime().exec("cmd \\c ")а потім використовую функцію перейменування командного рядка, щоб фактично перейменовувати файли. Це набагато гнучкіше, наприклад, якщо ви хочете перейменувати розширення всіх txt-файлів у директорії, щоб просто переписати це у вихідний потік:

перейменувати * .txt * .bak

Я знаю, що це не хороше рішення, але, мабуть, воно завжди працювало для мене, набагато краще, ніж підтримка вбудованої Java.


Супер, це набагато краще! Дякую! :-)
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, нічого не робить, якщо існуючий файл не існує, але, очевидно, можна було б краще інструментувати це виправити.


2

У мене була подібна проблема. Файл скопійовано, а рухається в Windows, але добре працює в Linux. Я вирішив проблему, закривши відкритий файлInputStream перед викликом 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

Я знаю, що це відмовно, але альтернативою є створення скрипта bat, який видає щось просте, таке як "УСПІХ" або "ПОМИЛКА", викликає його, чекає його виконання, а потім перевіряє результати.

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

Ця тема може бути цікавою. Перевірте також клас Process, як читати вихідні дані консолі іншого процесу.


-2

Ви можете спробувати роботизовану копію . Це не зовсім "перейменування", але воно дуже надійне.

Робокопія призначена для надійного віддзеркалення каталогів або дерев каталогів. Він має функції, що забезпечують копіювання всіх атрибутів і властивостей NTFS, і включає додатковий код перезапуску для мережевих з'єднань, що піддаються порушенню.


Дякую. Але оскільки роботизована копія не є бібліотекою Java, можливо, було б не дуже просто (згрупувати її та) використовувати з мого коду Java ...
Jonik,

-2

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

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

Це визначено в kernel32.dll.


1
Я відчуваю, що переживання цієї проблеми в JNI перевищує зусилля, необхідні для загортання робокопії в декоратор процесів.
Кевін Монтроуз

так, це ціна, яку ви платите за абстракцію - і коли вона витікає, вона витікає добре = 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 () не працює надійно; це вся суть питання.
Джонік
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.