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ź .

Jonik
źródło
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);
}
Alan
źródło
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.

Jonik
źródło
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 .

MykennaC
źródło
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.

wuppi
źródło
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

Kactus
źródło
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();
          }
    }
}
szalonego konia
źródło
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.

Johnydep
źródło
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ć.

bagaż
źródło
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);
Tharaka
źródło
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) {
           ...
Marcus Becker
źródło
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.

Ravi Wallau
źródło
-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.

Anton Gogolev
źródło
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.

Blindy
źródło
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.

iltaf khalid
źródło
11
Istnieją przypadki, w których funkcja renameTo () nie działa niezawodnie; o to właśnie chodzi w tym pytaniu.
Jonik