Thay thế File.renameTo () đáng tin cậy trên Windows?


92

File.renameTo()Có vẻ như Java có vấn đề, đặc biệt là trên Windows. Như tài liệu API cho biết,

Nhiều khía cạnh của hoạt động của phương pháp này vốn phụ thuộc vào nền tảng: Thao tác đổi tên có thể không thể di chuyển tệp từ hệ thống tệp này sang hệ thống tệp khác, nó có thể không phải là nguyên tử và có thể không thành công nếu tệp có tên đường dẫn trừu tượng đích đã tồn tại. Giá trị trả về phải luôn được kiểm tra để đảm bảo rằng thao tác đổi tên đã thành công.

Trong trường hợp của tôi, là một phần của quy trình nâng cấp, tôi cần di chuyển (đổi tên) một thư mục có thể chứa hàng gigabyte dữ liệu (rất nhiều thư mục con và tệp có kích thước khác nhau). Việc di chuyển luôn được thực hiện trong cùng một phân vùng / ổ đĩa, vì vậy không cần thực sự di chuyển tất cả các tệp trên đĩa.

Không nên có bất kỳ khóa tệp nào đối với nội dung của dir được di chuyển, nhưng vẫn thường, renameTo () không thực hiện được công việc của nó và trả về false. (Tôi chỉ đoán rằng có lẽ một số khóa tệp hết hạn hơi tùy tiện trên Windows.)

Hiện tại tôi có một phương pháp dự phòng sử dụng sao chép & xóa, nhưng điều này rất tệ vì có thể mất rất nhiều thời gian, tùy thuộc vào kích thước của thư mục. Tôi cũng đang xem xét đơn giản ghi lại thực tế là người dùng có thể di chuyển thư mục theo cách thủ công để tránh phải chờ hàng giờ, có thể xảy ra. Nhưng Con đường đúng rõ ràng sẽ là một thứ gì đó tự động và nhanh chóng.

Vì vậy, câu hỏi của tôi là, bạn có biết một phương pháp thay thế, đáng tin cậy để thực hiện chuyển đổi / đổi tên nhanh chóng với Java trên Windows , với JDK thuần túy hoặc một số thư viện bên ngoài. Hoặc nếu bạn biết một cách dễ dàng để phát hiện và giải phóng bất kỳ khóa tệp nào cho một thư mục nhất định và tất cả nội dung của nó (có thể là hàng nghìn tệp riêng lẻ), điều đó cũng sẽ ổn.


Chỉnh sửa : Trong trường hợp cụ thể này, có vẻ như chúng tôi đã sử dụng chỉ renameTo()bằng cách tính đến một vài điều khác; xem câu trả lời này .


3
Bạn có thể đợi / sử dụng JDK 7, hỗ trợ hệ thống tệp tốt hơn nhiều.
akarnokd

@ kd304, thực sự là tôi không thể chờ đợi hoặc sử dụng phiên bản truy cập sớm, nhưng thật thú vị khi biết điều gì đó tương tự đang được thực hiện!
Jonik

Câu trả lời:


52

Xem thêm Files.move()phương pháp trong JDK 7.

Một ví dụ:

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
may Java7 không phải lúc nào câu trả lời (như 42 là)
wuppi

1
Ngay cả trên ubuntu, JDK7, chúng tôi cũng gặp phải vấn đề này khi chạy mã trên EC2 với bộ nhớ EBS. File.renameTo không thành công và File.canWrite cũng vậy.
saurabheights

Lưu ý rằng điều này không đáng tin cậy như File # renameTo (). Nó chỉ đưa ra một lỗi hữu ích hơn khi nó không thành công. Cách duy nhất hợp lý đáng tin cậy mà tôi đã tìm thấy là sao chép tệp với Files # copy sang tên mới, sau đó xóa bản gốc bằng cách sử dụng Files # delete (việc xóa chính nó cũng có thể không thành công, vì lý do tương tự việc di chuyển Files # có thể không thành công) .
jwenting

26

Đối với những gì nó đáng giá, một số quan điểm khác:

  1. Trên Windows, renameTo()dường như không thành công nếu thư mục đích tồn tại, ngay cả khi nó trống. Điều này làm tôi ngạc nhiên, như tôi đã thử trên Linux, nơi renameTo()đã thành công nếu mục tiêu tồn tại, miễn là nó trống.

    (Rõ ràng là tôi không nên cho rằng loại thứ này hoạt động giống nhau trên các nền tảng; đây chính xác là những gì Javadoc cảnh báo.)

  2. Nếu bạn nghi ngờ có thể có một số khóa tệp còn tồn tại, hãy đợi một chút trước khi chuyển / đổi tên có thể hữu ích. (Tại một điểm trong trình cài đặt / nâng cấp của chúng tôi, chúng tôi đã thêm hành động "ngủ" và thanh tiến trình không xác định trong khoảng 10 giây, vì có thể có một dịch vụ bị treo trên một số tệp). Thậm chí có thể thực hiện một cơ chế thử lại đơn giản thử renameTo(), và sau đó đợi một khoảng thời gian (có thể tăng dần), cho đến khi thao tác thành công hoặc đạt đến thời gian chờ nào đó.

Trong trường hợp của tôi, hầu hết các vấn đề dường như đã được giải quyết bằng cách tính đến cả hai điều trên, vì vậy chúng ta sẽ không cần thực hiện một cuộc gọi kernel gốc, hoặc một số điều tương tự.


2
Tôi đang chấp nhận câu trả lời của riêng mình, vì nó mô tả những gì đã giúp ích trong trường hợp của chúng tôi. Tuy nhiên, nếu ai đó đưa ra câu trả lời tuyệt vời cho vấn đề chung hơn với renameTo (), hãy đăng và tôi sẽ rất vui khi xem xét lại câu trả lời được chấp nhận.
Jonik

4
6,5 năm sau, tôi nghĩ rằng đã đến lúc chấp nhận câu trả lời JDK 7 , đặc biệt là vì rất nhiều người coi nó là hữu ích. =)
Jonik

19

Bài đăng ban đầu yêu cầu "một phương pháp thay thế, đáng tin cậy để thực hiện chuyển / đổi tên nhanh chóng với Java trên Windows, với JDK thuần túy hoặc một số thư viện bên ngoài."

Một tùy chọn khác chưa được đề cập ở đây là v1.3.2 hoặc mới hơn của thư viện apache.commons.io , bao gồm FileUtils.moveFile () .

Nó ném một IOException thay vì trả về boolean false khi có lỗi.

Xem thêm phản hồi của big lep trong chủ đề khác này .


2
Ngoài ra, có vẻ như JDK 1.7 sẽ bao gồm hỗ trợ I / O hệ thống tệp tốt hơn. Kiểm tra java.nio.file.Path.moveTo (): java.sun.com/javase/7/docs/api/java/nio/file/Path.html
MykennaC

2
JDK 1.7 không có phương phápjava.nio.file.Path.moveTo()
Malte Schwerhoff

5

Trong trường hợp của tôi, nó dường như là một đối tượng đã chết trong ứng dụng của riêng tôi, nó giữ một phần xử lý cho tệp đó. Vì vậy, giải pháp đó đã làm việc cho tôi:

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

Ưu điểm: nó khá nhanh chóng, vì không có Thread.sleep () với thời gian được mã hóa cứng cụ thể.

Nhược điểm: giới hạn 20 là một số mã cứng. Trong tất cả các thử nghiệm của tôi, i = 1 là đủ. Nhưng để chắc chắn tôi đã để nó ở tuổi 20.


1
Tôi đã làm một điều tương tự, nhưng với chế độ ngủ 100ms trong vòng lặp.
Lawrence Dol

4

Tôi biết điều này có vẻ hơi khó hiểu, nhưng đối với những gì tôi cần nó, có vẻ như người đọc và người viết không có vấn đề gì khi tạo tệp.

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

}

Hoạt động tốt cho các tệp văn bản nhỏ như một phần của trình phân tích cú pháp, chỉ cần đảm bảo oldName và newName là đường dẫn đầy đủ đến các vị trí tệp.

Chúc mừng Kactus


4

Đoạn mã sau KHÔNG phải là 'thay thế' nhưng đã hoạt động đáng tin cậy đối với tôi trên cả môi trường Windows và 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, mã này xóa srcFile ngay cả khi renameTo (hoặc destFile.delete) không thành công và phương thức ném IOException; Tôi không chắc đó có phải là một ý kiến ​​hay không.
Jonik

1
@Jonik, Thanx, mã cố định để không xóa tệp src nếu đổi tên không thành công.
crazy horse,

Cảm ơn bạn đã chia sẻ điều này đã khắc phục sự cố đổi tên của tôi trên windows.
BillMan

3

Trên windows tôi sử dụng Runtime.getRuntime().exec("cmd \\c ")và sau đó sử dụng chức năng đổi tên dòng lệnh để thực sự đổi tên tệp. Nó linh hoạt hơn nhiều, ví dụ: nếu bạn muốn đổi tên phần mở rộng của tất cả các tệp txt trong một dir thành bak, chỉ cần viết điều này vào luồng đầu ra:

đổi tên * .txt * .bak

Tôi biết đó không phải là một giải pháp tốt nhưng rõ ràng nó luôn hoạt động với tôi, tốt hơn nhiều so với hỗ trợ nội tuyến Java.


Siêu, điều này tốt hơn nhiều! Cảm ơn! :-)
gaffcz

2

Tại sao không....

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

hoạt động trên nwindows 7, không có gì nếu hiện tại không tồn tại, nhưng rõ ràng có thể là công cụ tốt hơn để sửa lỗi này.


2

Tôi đã có một vấn đề tương tự. Tệp được sao chép thay vì di chuyển trên Windows nhưng hoạt động tốt trên Linux. Tôi đã khắc phục sự cố bằng cách đóng tệp đã mởInputStream trước khi gọi renameTo (). Đã thử nghiệm trên Windows XP.

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

1

Trong trường hợp của tôi, lỗi nằm trong đường dẫn của thư mục mẹ. Có thể là một lỗi, tôi đã phải sử dụng chuỗi con để có được một đường dẫn chính xác.

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

0

Tôi biết điều đó thật tệ, nhưng một giải pháp thay thế là tạo một tập lệnh dơi xuất ra một cái gì đó đơn giản như "THÀNH CÔNG" hoặc "LỖI", gọi nó, đợi nó được thực thi và sau đó kiểm tra kết quả của nó.

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

Chủ đề này có thể thú vị. Cũng kiểm tra lớp Quy trình về cách đọc đầu ra bảng điều khiển của một quy trình khác.


-2

Bạn có thể thử nội soi tự động . Đây không chính xác là "đổi tên", nhưng nó rất đáng tin cậy.

Robocopy được thiết kế để phản chiếu đáng tin cậy các thư mục hoặc cây thư mục. Nó có các tính năng để đảm bảo tất cả các thuộc tính và thuộc tính NTFS đều được sao chép và bao gồm mã khởi động lại bổ sung cho các kết nối mạng có thể bị gián đoạn.


Cảm ơn. Nhưng kể từ robocopy không phải là một thư viện Java, nó có lẽ sẽ không phải là rất dễ dàng để (bó nó và) sử dụng nó từ mã Java của tôi ...
Jonik

-2

Để di chuyển / đổi tên tệp, bạn có thể sử dụng chức năng này:

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

Nó được định nghĩa trong kernel32.dll.


1
Tôi cảm thấy việc trải qua khó khăn khi gói điều này trong JNI còn lớn hơn nỗ lực cần thiết để gói bản sao robot trong trình trang trí Quy trình.
Kevin Montrose

vâng, đây là mức giá bạn phải trả cho sự trừu tượng - và khi nó bị rò rỉ, nó rò rỉ tốt = D
Chii

Cảm ơn, tôi có thể xem xét điều này nếu nó không quá phức tạp. Tôi đã không bao giờ sử dụng JNI, và không thể tìm thấy ví dụ tốt về cách gọi một hàm kernel Windows trên SO, vì vậy tôi đăng câu hỏi này: stackoverflow.com/questions/1000723/...
Jonik

Bạn có thể thử một trình bao bọc JNI chung chung như johannburkard.de/software/nativecall vì đây là một lệnh gọi hàm khá đơn giản.
Peter Smith,

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

Trên đây là mã đơn giản. Tôi đã thử nghiệm trên windows 7 và hoạt động hoàn toàn tốt.


11
Có những trường hợp renameTo () không hoạt động đáng tin cậy; đó là toàn bộ điểm của câu hỏi.
Jonik
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.