Windows'ta Güvenilir File.renameTo () alternatifi?


93

File.renameTo()Görünüşe göre Java's sorunlu, özellikle Windows'ta. Gibi API belgeleri diyor

Bu yöntemin davranışının birçok yönü, doğası gereği platforma bağlıdır: Yeniden adlandırma işlemi bir dosyayı bir dosya sisteminden diğerine taşıyamayabilir, atomik olmayabilir ve hedef soyut yol adına sahip bir dosya varsa başarılı olmayabilir. zaten var. Yeniden adlandırma işleminin başarılı olduğundan emin olmak için dönüş değeri her zaman kontrol edilmelidir.

Benim durumumda, bir yükseltme prosedürünün parçası olarak, gigabaytlarca veri (çok sayıda alt dizin ve çeşitli boyutlarda dosyalar) içerebilen bir dizini taşımam (yeniden adlandırmam) gerekiyor. Taşıma her zaman aynı bölüm / sürücü içinde yapılır, bu nedenle diskteki tüm dosyaları fiziksel olarak taşımaya gerek yoktur.

Orada olmamalı Taşınacak dir içeriğine herhangi bir dosya kilitleri olabilir, ama yine de, oldukça sık, renameTo () kendi iş ve döner false başaramazsa. (Sadece Windows'ta bazı dosya kilitlerinin süresinin biraz keyfi olarak dolacağını tahmin ediyorum.)

Şu anda kopyalamayı ve silmeyi kullanan bir geri dönüş yöntemim var, ancak bu , klasörün boyutuna bağlı olarak çok zaman alabileceği için berbat . Ayrıca, potansiyel olarak saatlerce beklemekten kaçınmak için kullanıcının klasörü manuel olarak taşıyabileceği gerçeğini belgelemeyi de düşünüyorum. Ancak Doğru Yol açıkça otomatik ve hızlı bir şey olurdu.

Öyleyse sorum şu, Windows üzerinde Java ile basit JDK veya bazı harici kitaplıklar ile hızlı taşıma / yeniden adlandırma yapmak için alternatif, güvenilir bir yaklaşım biliyor musunuz ? Veya belirli bir klasör ve tüm içeriği için (muhtemelen binlerce ayrı dosya) herhangi bir dosya kilidini tespit etmenin ve serbest bırakmanın kolay bir yolunu biliyorsanız , bu da iyi olur.


Düzenleme : Bu özel durumda, sadece renameTo()birkaç şeyi daha hesaba katarak kullanmaktan kurtulmuş gibi görünüyor ; bu cevaba bakın .


3
Çok daha iyi dosya sistemi desteğine sahip olan JDK 7'yi bekleyebilir / kullanabilirsiniz.
akarnokd

@ kd304, aslında bekleyemem veya erken erişim sürümünü kullanamıyorum, ancak bunun gibi bir şeyin yolda olduğunu bilmek ilginç!
Jonik

Yanıtlar:


52

Ayrıca Files.move()JDK 7'deki yönteme bakın .

Bir örnek:

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
maalesef Java7 her zaman cevap değildir (42 gibi)
wuppi

1
Ubuntu, JDK7'de bile, EBS depolamalı EC2'de kod çalıştırırken bu sorunla karşılaştık. File.renameTo başarısız oldu ve File.canWrite da başarısız oldu.
saurabheights

Bunun File # renameTo () kadar güvenilmez olduğunu unutmayın. Sadece başarısız olduğunda daha kullanışlı bir hata veriyor. Bulduğum makul derecede güvenilir tek yol, dosyayı Dosyalar # kopyası ile yeni ada kopyalamak ve ardından orijinali Dosyalar # silme özelliğini kullanarak silmektir (Dosya # taşıma aynı nedenle başarısız olabilir) .
jwenting

26

Değeri ne olursa olsun, bazı başka fikirler:

  1. Windows'ta, renameTo()boş olsa bile hedef dizin varsa başarısız görünüyor. Bu beni, Linux'ta denediğim renameTo()gibi, boş olduğu sürece hedef varsa başarılı oldu.

    (Açıkçası, bu tür şeylerin platformlarda aynı şekilde çalıştığını varsaymamalıydım; Javadoc'un uyardığı tam olarak budur.)

  2. Bazı dosya kilitleri olabileceğinden şüpheleniyorsanız, taşıma / yeniden adlandırma işleminden önce biraz beklemeniz yardımcı olabilir . (Yükleyicimizin / yükselticimizin bir noktasında, bazı dosyalarda asılı bir hizmet olabileceğinden, yaklaşık 10 saniye süreyle bir "uyku" eylemi ve belirsiz bir ilerleme çubuğu ekledik). Belki de deneyen renameTo()ve ardından işlem başarılı olana veya zaman aşımına ulaşılana kadar bir süre (belki kademeli olarak artan) bekleyen basit bir yeniden deneme mekanizması bile yapın .

Benim durumumda, çoğu sorun yukarıdakilerin her ikisi de dikkate alınarak çözülmüş gibi görünüyor, bu nedenle yerel bir çekirdek çağrısı yapmamıza veya buna benzer bir şey yapmamıza gerek kalmayacak.


2
Durumumuza neyin yardımcı olduğunu açıkladığı için şimdilik kendi cevabımı kabul ediyorum. Yine de birisi renameTo () ile ilgili daha genel soruna harika bir yanıt bulursa, gönderi yapmaktan çekinmeyin ve kabul edilen yanıtı yeniden değerlendirmekten memnuniyet duyarım.
Jonik

4
6.5 yıl sonra, bunun yerine JDK 7 yanıtını kabul etmenin zamanı geldiğini düşünüyorum , özellikle de pek çok kişi bunu faydalı bulduğu için. =)
Jonik

19

Orijinal gönderi "düz JDK veya bazı harici kitaplıklarla Windows'ta Java ile hızlı bir taşıma / yeniden adlandırma yapmak için alternatif, güvenilir bir yaklaşım" istedi.

Burada henüz bahsedilmeyen bir başka seçenek , FileUtils.moveFile () içeren apache.commons.io kitaplığının v1.3.2 veya sonraki sürümleridir .

Hata durumunda boolean false döndürmek yerine bir IOException oluşturur.

Ayrıca bu diğer konu başlığındaki büyük lep'in cevabına bakın .


2
Ayrıca, JDK 1.7'nin daha iyi dosya sistemi G / Ç desteği içereceği görülüyor. : Java.nio.file.Path.moveTo () kontrol edin java.sun.com/javase/7/docs/api/java/nio/file/Path.html
MykennaC

2
JDK java.nio.file.Path.moveTo()
1.7'nin

5

Benim durumumda, kendi uygulamamda bu dosyanın işleyişini sağlayan ölü bir nesne gibi görünüyordu. Böylece bu çözüm benim için çalıştı:

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

Avantajı: Belirli bir kodlanmış süreye sahip Thread.sleep () olmadığı için oldukça hızlıdır.

Dezavantaj: Bu 20 sınırı, kodlanmış bir sayıdır. Tüm testlerimde i = 1 yeterlidir. Ama emin olmak için 20'de bıraktım.


1
Ben de benzer bir şey yaptım, ancak döngüde 100 ms uyku ile.
Lawrence Dol

4

Bunun biraz karmaşık göründüğünü biliyorum, ancak ihtiyacım olan şey için, ara belleğe alınmış okuyucular ve yazarların dosyaları oluşturmada herhangi bir sorunu yok gibi görünüyor.

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

}

Bir ayrıştırıcının parçası olarak küçük metin dosyaları için iyi çalışır, sadece eskiAdı ve yeniAdı'nın dosya konumlarına giden tam yollar olduğundan emin olun.

Şerefe Kactus


4

Aşağıdaki kod parçası bir 'alternatif' DEĞİLDİR ancak benim için hem Windows hem de Linux ortamlarında güvenilir bir şekilde çalıştı:

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, bu kod, renameTo (veya destFile.delete) başarısız olsa ve yöntem IOException atsa bile srcFile öğesini siler; Bunun iyi bir fikir olup olmadığından emin değilim.
Jonik

1
@Jonik, Thanx, yeniden adlandırma başarısız olursa src dosyasını silmemek için sabit kod.
çılgın at

Bu yeniden adlandırma sorunumu Windows'ta paylaştığınız için teşekkürler.
BillMan

3

Windows'ta Runtime.getRuntime().exec("cmd \\c ")dosyaları yeniden adlandırmak için komut satırı yeniden adlandırma işlevini kullanıyorum ve kullanıyorum. Çok daha esnektir, örneğin bir dizindeki tüm txt dosyalarının uzantısını yeniden adlandırmak istiyorsanız, bunu çıkış akışına yazın:

* .txt * .bak olarak yeniden adlandır

Bunun iyi bir çözüm olmadığını biliyorum ama görünüşe göre benim için her zaman işe yaradı, Java inline desteğinden çok daha iyi.


Süper, bu çok daha iyi! Teşekkürler! :-)
gaffcz

2

Neden olmasın....

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 üzerinde çalışır, mevcutFile yoksa hiçbir şey yapmaz, ancak bunu düzeltmek için daha iyi bir araç olabilir.


2

Benzer bir sorun yaşadım. Dosya Windows'ta taşınmak yerine kopyalandı, ancak Linux'ta iyi çalıştı. RenameTo () 'yu çağırmadan önce açılmış fileInputStream'i kapatarak sorunu çözdüm. Windows XP'de test edilmiştir.

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

1

Benim durumumda, hata ana dizinin yolundaydı. Belki bir hata, doğru yolu bulmak için alt dizeyi kullanmak zorunda kaldım.

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

0

Bunun berbat olduğunu biliyorum, ancak bir alternatif, "BAŞARI" veya "HATA" gibi basit bir şey çıkaran, onu çağıran, yürütülmesini bekleyen ve ardından sonuçlarını kontrol eden bir bat betiği oluşturmaktır.

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

Bu konu ilginç olabilir. Farklı bir işlemin konsol çıktısının nasıl okunacağıyla ilgili olarak İşlem sınıfını da kontrol edin.


-2

Robocopy'yi deneyebilirsiniz . Bu tam olarak "yeniden adlandırmak" değildir, ancak çok güvenilirdir.

Robocopy, dizinlerin veya dizin ağaçlarının güvenilir şekilde yansıtılması için tasarlanmıştır. Tüm NTFS özniteliklerinin ve özelliklerinin kopyalanmasını sağlayan özelliklere sahiptir ve kesintiye uğrayabilecek ağ bağlantıları için ek yeniden başlatma kodu içerir.


Teşekkürler. Ancak robocopy bir Java kitaplığı olmadığından, onu Java
kodumdan

-2

Bir dosyayı taşımak / yeniden adlandırmak için bu işlevi kullanabilirsiniz:

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

Kernel32.dll'de tanımlanmıştır.


1
Bunu JNI'ye sarma zahmetinden geçmenin, bir Süreç dekoratöründe robocopy'yi tamamlamak için gereken çabadan daha büyük olduğunu hissediyorum.
Kevin Montrose

evet, soyutlama için ödediğiniz bedel bu - ve sızdırdığında iyi sızdırıyor = D
Chii

Teşekkürler, çok karmaşık olmazsa bunu düşünebilirim. JNI'yi hiç kullanmadım ve
SO'da

Bu oldukça basit bir işlev çağrısı olduğu için johannburkard.de/software/nativecall gibi genel bir JNI sarmalayıcısını deneyebilirsiniz .
Peter Smith

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

Yukarıdaki basit koddur. Windows 7'de test ettim ve gayet iyi çalışıyor.


11
RenameTo () yok durumlar vardır değil güvenilir bir şekilde çalışması; sorunun tüm noktası budur.
Jonik
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.