Niezawodna alternatywa File.renameTo () w systemie Windows?


92

File.renameTo()Wygląda na to, że Java jest problematyczna, zwłaszcza w systemie Windows. Jak mówi dokumentacja API ,

Wiele aspektów zachowania tej metody jest z natury zależnych od platformy: operacja zmiany nazwy może nie być w stanie przenieść pliku z jednego systemu plików na inny, może nie być atomowy i może się nie powieść, jeśli plik z docelową abstrakcyjną nazwą ścieżki już istnieje. Zawsze należy sprawdzać zwracaną wartość, aby upewnić się, że operacja zmiany nazwy zakończyła się pomyślnie.

W moim przypadku w ramach procedury aktualizacji muszę przenieść (zmienić nazwę) katalog, który może zawierać gigabajty danych (wiele podkatalogów i plików o różnych rozmiarach). Przenoszenie jest zawsze wykonywane na tej samej partycji / dysku, więc nie ma potrzeby fizycznego przenoszenia wszystkich plików na dysku.

Nie powinno być żadnych blokad plików w zawartości katalogu do przeniesienia, ale wciąż dość często funkcja renameTo () nie wykonuje swojego zadania i zwraca wartość false. (Zgaduję tylko, że być może niektóre blokady plików wygasają nieco arbitralnie w systemie Windows.)

Obecnie mam metodę awaryjną, która wykorzystuje kopiowanie i usuwanie, ale to jest do bani, ponieważ może to zająć dużo czasu, w zależności od rozmiaru folderu. Rozważam również po prostu udokumentowanie faktu, że użytkownik może ręcznie przenieść folder, aby potencjalnie uniknąć czekania godzinami. Ale Właściwa Droga byłaby oczywiście czymś automatycznym i szybkim.

Moje pytanie brzmi więc, czy znasz alternatywne, niezawodne podejście do szybkiego przenoszenia / zmiany nazwy w Javie w systemie Windows , albo za pomocą zwykłego JDK, albo jakiejś zewnętrznej biblioteki. Lub jeśli znasz łatwy sposób na wykrycie i zwolnienie blokad plików dla danego folderu i całej jego zawartości (prawdopodobnie tysięcy pojedynczych plików), to też byłoby w porządku.


Edycja : w tym konkretnym przypadku wydaje się, że uniknęliśmy używania renameTo(), biorąc pod uwagę kilka innych rzeczy; zobacz tę odpowiedź .


3
Możesz poczekać / użyć JDK 7, który ma znacznie lepszą obsługę systemu plików.
akarnokd

@ kd304, właściwie nie mogę się doczekać ani użyć wersji z wczesnym dostępem, ale interesujące jest wiedzieć, że coś takiego jest w drodze!
Jonik,

Odpowiedzi:


52

Zobacz także Files.move()metodę w JDK 7.

Przykład:

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
niestety Java7 nie zawsze jest odpowiedzią (tak jak 42)
wuppi

1
Nawet na Ubuntu, JDK7, napotkaliśmy ten problem podczas uruchamiania kodu na EC2 z pamięcią EBS. File.renameTo nie powiodło się, podobnie jak File.canWrite.
saurabheights

Pamiętaj, że jest to tak zawodne, jak File # renameTo (). Po prostu daje bardziej użyteczny błąd, gdy zawiedzie. Jedynym rozsądnie niezawodnym sposobem, jaki znalazłem, jest skopiowanie pliku z Files # copy do nowej nazwy, a następnie usunięcie oryginału za pomocą funkcji Files # delete (co może się nie powieść, z tego samego powodu, dla którego przeniesienie plików # może się nie powieść) .
jwenting

26

Warto, kilka dalszych pojęć:

  1. W systemie Windows renameTo()wydaje się nie powieść, jeśli katalog docelowy istnieje, nawet jeśli jest pusty. Zaskoczyło mnie to, ponieważ próbowałem na Linuksie, gdzie renameTo()udało się, jeśli cel istniał, o ile był pusty.

    (Oczywiście nie powinienem zakładać, że tego rodzaju rzeczy działają tak samo na różnych platformach; dokładnie o tym ostrzega Javadoc).

  2. Jeśli podejrzewasz, że mogą istnieć pewne utrzymujące się blokady plików, poczekaj chwilę, zanim przeniesienie / zmiana nazwy może pomóc. (W jednym miejscu w naszym instalatorze / uaktualniaczu dodaliśmy akcję "uśpienia" i nieokreślony pasek postępu przez około 10 sekund, ponieważ niektóre pliki mogą się zawiesić). Być może nawet wykonaj prosty mechanizm ponawiania, który próbuje renameTo(), a następnie czeka przez pewien okres (który może stopniowo się zwiększać), aż operacja się powiedzie lub osiągnięty zostanie limit czasu.

W moim przypadku wydaje się, że większość problemów została rozwiązana biorąc pod uwagę oba powyższe, więc w końcu nie będziemy musieli wykonywać natywnego wywołania jądra lub czegoś podobnego.


2
Akceptuję na razie własną odpowiedź, ponieważ opisuje, co pomogło w naszym przypadku. Mimo to, jeśli ktoś znajdzie świetną odpowiedź na bardziej ogólny problem z renameTo (), nie krępuj się pisać, a ja z przyjemnością ponownie rozważę zaakceptowaną odpowiedź.
Jonik

4
6,5 roku później myślę, że nadszedł czas, aby zamiast tego zaakceptować odpowiedź z JDK 7 , zwłaszcza że tak wiele osób uważa ją za pomocną. =)
Jonik

19

Oryginalny post zawierał prośbę o „alternatywne, niezawodne podejście do szybkiego przenoszenia / zmiany nazwy w Javie w systemie Windows, przy użyciu zwykłego JDK lub jakiejś zewnętrznej biblioteki”.

Inną opcją, o której tu jeszcze nie wspomniano, jest wersja 1.3.2 lub nowsza biblioteki apache.commons.io , która zawiera FileUtils.moveFile () .

Zgłasza IOException zamiast zwracać wartość logiczną false w przypadku błędu.

Zobacz także odpowiedź wielkiego lpa w tym innym wątku .


2
Wygląda również na to, że JDK 1.7 będzie zawierał lepszą obsługę wejścia / wyjścia systemu plików. Sprawdź java.nio.file.Path.moveTo (): java.sun.com/javase/7/docs/api/java/nio/file/Path.html
MykennaC

2
JDK 1.7 nie ma metodyjava.nio.file.Path.moveTo()
Malte Schwerhoff

5

W moim przypadku wydawało się, że był to martwy obiekt w mojej własnej aplikacji, która zachowała uchwyt do tego pliku. Więc to rozwiązanie zadziałało dla mnie:

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

Zaleta: jest dość szybka, ponieważ nie ma funkcji Thread.sleep () z konkretnym zakodowanym czasem.

Wada: ten limit 20 to pewna liczba zakodowana na stałe. We wszystkich moich testach wystarczy i = 1. Ale dla pewności zostawiłem to o 20.


1
Zrobiłem podobnie, ale ze snem 100 ms w pętli.
Lawrence Dol

4

Wiem, że wydaje się to trochę hakerskie, ale z powodu tego, do czego go potrzebowałem, wydaje się, że buforowani czytelnicy i pisarze nie mają problemu z tworzeniem plików.

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();
    }

}

Działa dobrze w przypadku małych plików tekstowych jako część parsera, po prostu upewnij się, że oldName i newName to pełne ścieżki do lokalizacji plików.

Pozdrawiam Kactus


4

Poniższy fragment kodu NIE jest „alternatywą”, ale sprawdził się niezawodnie zarówno w środowisku Windows, jak i 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
Hmm, ten kod usuwa srcFile, nawet jeśli operacja renameTo (lub destFile.delete) nie powiedzie się, a metoda zgłosi IOException; Nie jestem pewien, czy to dobry pomysł.
Jonik,

1
@Jonik, Thanx, naprawiono kod, aby nie usuwać pliku src, jeśli zmiana nazwy nie powiedzie się.
szalony koń

Dziękuję za udostępnienie tego, rozwiązałem problem ze zmianą nazwy w systemie Windows.
BillMan,

3

W systemie Windows używam, Runtime.getRuntime().exec("cmd \\c ")a następnie używam funkcji zmiany nazwy z wiersza poleceń, aby faktycznie zmienić nazwy plików. Jest znacznie bardziej elastyczny, np. Jeśli chcesz zmienić rozszerzenie wszystkich plików txt w katalogu do bak, po prostu napisz to do strumienia wyjściowego:

zmień nazwę * .txt * .bak

Wiem, że to nie jest dobre rozwiązanie, ale najwyraźniej zawsze działało na mnie, znacznie lepiej niż obsługa wbudowanej Java.


Super, to jest o wiele lepsze! Dzięki! :-)
gaffcz

2

Dlaczego nie....

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);
        }
}

działa na nwindows 7, nic nie robi, jeśli nie istnieje plik existingFile, ale oczywiście mógłby być lepiej wyposażony, aby to naprawić.


2

Miałem podobny problem. Plik został skopiowany raczej w ruchu w systemie Windows, ale działał dobrze w systemie Linux. Rozwiązałem problem, zamykając otwarty plik FileInputStream przed wywołaniem funkcji renameTo (). Przetestowano w systemie Windows XP.

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

1

W moim przypadku błąd był w ścieżce katalogu nadrzędnego. Może to błąd, musiałem użyć podciągu, aby uzyskać poprawną ścieżkę.

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

0

Wiem, że to jest do bani, ale alternatywą jest utworzenie skryptu nietoperza, który wyświetla coś prostego, takiego jak „SUKCES” lub „BŁĄD”, wywołanie go, poczekanie na wykonanie i sprawdzenie wyników.

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

Ten wątek może być interesujący. Sprawdź również klasę Process, aby dowiedzieć się, jak odczytywać dane wyjściowe konsoli z innego procesu.


-2

Możesz spróbować robocopy . Nie jest to dokładnie „zmiana nazwy”, ale jest to bardzo niezawodne.

Robocopy jest przeznaczony do niezawodnego tworzenia kopii lustrzanych katalogów lub drzew katalogów. Posiada funkcje zapewniające kopiowanie wszystkich atrybutów i właściwości NTFS oraz zawiera dodatkowy kod restartu dla połączeń sieciowych podlegających zakłóceniom.


Dzięki. Ale ponieważ robocopy nie jest biblioteką Java, prawdopodobnie nie byłoby łatwo (spakować go i) użyć go z mojego kodu Java ...
Jonik

-2

Aby przenieść / zmienić nazwę pliku, możesz użyć tej funkcji:

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

Jest zdefiniowany w kernel32.dll.


1
Wydaje mi się, że trud zawijania tego w JNI jest większy niż wysiłek potrzebny do owinięcia robocopy w dekoratorze Process.
Kevin Montrose

tak, to jest cena, jaką płacisz za abstrakcję - a kiedy przecieka, przecieka dobrze = D
Chii

Dzięki, mogę to rozważyć, jeśli nie będzie to zbyt skomplikowane. Nigdy nie korzystałem z JNI i nie mogłem znaleźć dobrych przykładów wywoływania funkcji jądra systemu Windows w SO, więc opublikowałem to pytanie: stackoverflow.com/questions/1000723/ ...
Jonik

Możesz wypróbować ogólne opakowanie JNI, takie jak johannburkard.de/software/nativecall, ponieważ jest to całkiem proste wywołanie funkcji.
Peter Smith

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

Powyższy kod to prosty kod. Testowałem na Windows 7 i działa doskonale.


11
Istnieją przypadki, w których funkcja renameTo () nie działa niezawodnie; o to właśnie chodzi w tym pytaniu.
Jonik
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.