Alternatif File.renameTo () yang andal di Windows?

92

Java File.renameTo()bermasalah, terutama pada Windows, tampaknya. Seperti yang dikatakan dalam dokumentasi API ,

Banyak aspek perilaku metode ini secara inheren bergantung pada platform: Operasi penggantian nama mungkin tidak dapat memindahkan file dari satu sistem file ke sistem file lainnya, mungkin tidak bersifat atomik, dan mungkin tidak berhasil jika file dengan nama jalur abstrak tujuan sudah ada. Nilai yang dikembalikan harus selalu diperiksa untuk memastikan bahwa operasi penggantian nama berhasil.

Dalam kasus saya, sebagai bagian dari prosedur peningkatan, saya perlu memindahkan (mengganti nama) direktori yang mungkin berisi gigabyte data (banyak subdirektori dan file dengan berbagai ukuran). Pemindahan selalu dilakukan dalam partisi / drive yang sama, jadi tidak perlu memindahkan semua file secara fisik ke disk.

Ada seharusnya tidak menjadi file kunci untuk isi dir yang akan dipindahkan, tapi masih, cukup sering, renameTo () gagal untuk melakukan pekerjaan dan kembali nya palsu. (Saya hanya menebak bahwa mungkin beberapa kunci file kedaluwarsa agak sewenang-wenang di Windows.)

Saat ini saya memiliki metode fallback yang menggunakan penyalinan & penghapusan, tetapi ini menyebalkan karena mungkin membutuhkan banyak waktu, tergantung pada ukuran folder. Saya juga mempertimbangkan untuk mendokumentasikan fakta bahwa pengguna dapat memindahkan folder secara manual untuk menghindari menunggu berjam-jam, berpotensi. Tetapi Jalan yang Benar jelas akan menjadi sesuatu yang otomatis dan cepat.

Jadi pertanyaan saya adalah, apakah Anda tahu alternatif, pendekatan yang andal untuk melakukan perpindahan cepat / mengganti nama dengan Java di Windows , baik dengan JDK biasa atau beberapa perpustakaan eksternal. Atau jika Anda tahu cara mudah untuk mendeteksi dan melepaskan kunci file apa pun untuk folder tertentu dan semua isinya (mungkin ribuan file individual), itu juga bagus.


Sunting : Dalam kasus khusus ini, tampaknya kami lolos hanya renameTo()dengan mempertimbangkan beberapa hal lagi; lihat jawaban ini .

Jonik
sumber
3
Anda dapat menunggu / menggunakan JDK 7, yang memiliki dukungan sistem file yang jauh lebih baik.
akarnokd
@ kd304, sebenarnya saya tidak sabar atau menggunakan versi akses awal, tetapi menarik untuk mengetahui sesuatu seperti itu sedang dalam proses!
Jonik

Jawaban:

52

Lihat juga Files.move()metode di JDK 7.

Sebuah contoh:

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
sumber
7
sayangnya Java7 tidak selalu jawabannya (seperti 42 adalah)
wuppi
1
Bahkan di ubuntu, JDK7, kami menghadapi masalah ini saat menjalankan kode pada EC2 dengan penyimpanan EBS. File.renameTo gagal dan begitu pula File.canWrite.
saurabheights
Ingatlah bahwa ini tidak dapat diandalkan seperti File # renameTo (). Itu hanya memberikan kesalahan yang lebih berguna ketika gagal. Satu-satunya cara yang cukup andal yang saya temukan adalah menyalin file dengan Files # copy ke nama baru, dan kemudian menghapus yang asli menggunakan Files # delete (yang menghapus sendiri mungkin gagal juga, karena alasan yang sama Pemindahan file # mungkin gagal) .
jwenting
26

Untuk apa nilainya, beberapa pengertian lebih lanjut:

  1. Pada Windows, renameTo()tampaknya gagal jika direktori target ada, meskipun kosong. Ini mengejutkan saya, seperti yang pernah saya coba di Linux, di mana renameTo()berhasil jika target ada, selama masih kosong.

    (Jelas saya seharusnya tidak menganggap hal semacam ini berfungsi sama di seluruh platform; inilah yang diperingatkan Javadoc.)

  2. Jika Anda menduga mungkin ada beberapa kunci file yang tertinggal, tunggu sebentar sebelum memindahkan / mengganti nama mungkin membantu. (Dalam satu titik di pemasang / pemutakhiran kami, kami menambahkan tindakan "tidur" dan bilah kemajuan tak tentu selama sekitar 10 detik, karena mungkin ada layanan yang tergantung pada beberapa file). Bahkan mungkin melakukan mekanisme coba lagi sederhana yang mencoba renameTo(), dan kemudian menunggu beberapa saat (yang mungkin meningkat secara bertahap), hingga operasi berhasil atau batas waktu tercapai.

Dalam kasus saya, sebagian besar masalah tampaknya telah diselesaikan dengan mempertimbangkan kedua hal di atas, jadi kita tidak perlu melakukan panggilan kernel asli, atau semacamnya, setelah semua.

Jonik
sumber
2
Saya menerima jawaban saya sendiri untuk saat ini, karena itu menggambarkan apa yang membantu dalam kasus kami. Namun, jika seseorang memberikan jawaban yang bagus untuk masalah yang lebih umum dengan renameTo (), jangan ragu untuk memposting dan saya akan dengan senang hati mempertimbangkan kembali jawaban yang diterima.
Jonik
4
6,5 tahun kemudian, saya pikir inilah saatnya untuk menerima jawaban JDK 7 , terutama karena banyak orang menganggapnya berguna. =)
Jonik
19

Posting asli meminta "pendekatan alternatif yang andal untuk melakukan pemindahan / penggantian nama cepat dengan Java di Windows, baik dengan JDK biasa atau beberapa library eksternal."

Opsi lain yang belum disebutkan di sini adalah v1.3.2 atau yang lebih baru dari pustaka apache.commons.io , yang menyertakan FileUtils.moveFile () .

Ini melempar IOException alih-alih menampilkan boolean false saat terjadi kesalahan.

Lihat juga respon lep besar di utas lainnya ini .

MykennaC
sumber
2
Selain itu, sepertinya JDK 1.7 akan menyertakan dukungan I / O sistem file yang lebih baik. Lihat java.nio.file.Path.moveTo (): java.sun.com/javase/7/docs/api/java/nio/file/Path.html
MykennaC
2
JDK 1.7 tidak memiliki metodejava.nio.file.Path.moveTo()
Malte Schwerhoff
5

Dalam kasus saya, ini tampaknya menjadi objek mati dalam aplikasi saya sendiri, yang menyimpan pegangan ke file itu. Jadi solusi itu berhasil untuk saya:

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

Keuntungan: cukup cepat, karena tidak ada Thread.sleep () dengan waktu hardcode tertentu.

Kerugian: batas 20 itu adalah beberapa angka hardcode. Dalam semua pengujian saya, i = 1 sudah cukup. Tetapi untuk memastikan saya meninggalkannya pada 20.

wuppi.dll
sumber
1
Saya melakukan hal serupa, tetapi dengan sleep 100ms di loop.
Lawrence Dol
4

Saya tahu ini tampaknya sedikit hacky, tetapi untuk apa saya membutuhkannya, tampaknya pembaca dan penulis buffer tidak memiliki masalah dalam membuat file.

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

}

Berfungsi dengan baik untuk file teks kecil sebagai bagian dari parser, cukup pastikan oldName dan newName adalah jalur lengkap ke lokasi file.

Cheers Kactus

Kaktus
sumber
4

Potongan kode berikut BUKAN 'alternatif' tetapi telah bekerja dengan andal untuk saya di lingkungan Windows dan 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();
          }
    }
}
crazy horse
sumber
2
Hmm, kode ini menghapus srcFile meskipun renameTo (atau destFile.delete) gagal dan metode menampilkan IOException; Saya tidak yakin apakah itu ide yang bagus.
Jonik
1
@Jonik, Thanx, kode tetap untuk tidak menghapus file src jika penggantian nama gagal.
kuda gila
Terima kasih telah berbagi ini memperbaiki masalah penggantian nama saya di windows.
BillMan
3

Pada windows saya menggunakan Runtime.getRuntime().exec("cmd \\c ")dan kemudian menggunakan fungsi ganti nama baris perintah untuk benar-benar mengganti nama file. Ini jauh lebih fleksibel, misalnya jika Anda ingin mengganti nama ekstensi dari semua file txt di direktori menjadi bak tulis saja ini ke output stream:

ganti nama * .txt * .bak

Saya tahu ini bukan solusi yang baik tetapi tampaknya itu selalu berhasil untuk saya, jauh lebih baik daripada dukungan inline Java.

Johnydep
sumber
Super, ini jauh lebih baik! Terima kasih! :-)
gaffcz
2

Kenapa tidak....

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

bekerja pada nwindows 7, tidak melakukan apa-apa jika existingFile tidak ada, tapi jelas bisa diinstrumentasi lebih baik untuk memperbaikinya.

kasus
sumber
2

Saya memiliki masalah serupa. File disalin agak bergerak di Windows tetapi berfungsi dengan baik di Linux. Saya memperbaiki masalah ini dengan menutup fileInputStream yang dibuka sebelum memanggil renameTo (). Diuji pada Windows XP.

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

Dalam kasus saya, kesalahan berada di jalur direktori induk. Mungkin bug, saya harus menggunakan substring untuk mendapatkan jalur yang benar.

        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
sumber
0

Saya tahu itu menyebalkan, tetapi alternatifnya adalah membuat skrip kelelawar yang menghasilkan sesuatu yang sederhana seperti "SUKSES" atau "KESALAHAN", panggil, tunggu sampai dieksekusi dan kemudian periksa hasilnya.

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

Utas ini mungkin menarik. Periksa juga kelas Proses tentang cara membaca keluaran konsol dari proses yang berbeda.

Ravi Wallau
sumber
-2

Anda dapat mencoba robocopy . Ini tidak persis "mengganti nama", tetapi sangat dapat diandalkan.

Robocopy dirancang untuk pencerminan direktori atau pohon direktori yang andal. Ini memiliki fitur untuk memastikan semua atribut dan properti NTFS disalin, dan termasuk kode restart tambahan untuk koneksi jaringan yang mengalami gangguan.

Anton Gogolev
sumber
Terima kasih. Tetapi karena robocopy bukan pustaka Java, mungkin tidak akan mudah (memaketkannya dan) menggunakannya dari kode Java saya ...
Jonik
-2

Untuk memindahkan / mengganti nama file Anda dapat menggunakan fungsi ini:

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

Ini didefinisikan di kernel32.dll.

Buta
sumber
1
Saya merasa mengalami kesulitan untuk membungkus ini di JNI lebih besar daripada upaya yang diperlukan untuk menyelesaikan robocopy di dekorator Proses.
Kevin Montrose
ya, ini harga yang Anda bayar untuk abstraksi - dan ketika bocor, bocor bagus = D
Chii
Terima kasih, saya mungkin mempertimbangkan ini jika tidak terlalu rumit. Saya belum pernah menggunakan JNI, dan tidak dapat menemukan contoh yang baik untuk memanggil fungsi kernel Windows di SO, jadi saya memposting pertanyaan ini: stackoverflow.com/questions/1000723/…
Jonik
Anda dapat mencoba pembungkus JNI generik seperti johannburkard.de/software/nativecall karena ini adalah pemanggilan fungsi yang cukup sederhana.
Peter Smith
-8
 File srcFile = new File(origFilename);
 File destFile = new File(newFilename);
 srcFile.renameTo(destFile);

Di atas adalah kode sederhana. Saya telah menguji pada windows 7 dan bekerja dengan baik.

iltaf khalid
sumber
11
Ada kasus di mana renameTo () tidak bekerja dengan andal; itulah inti dari pertanyaannya.
Jonik