ทางเลือก File.renameTo () ที่เชื่อถือได้บน Windows?


92

File.renameTo()ดูเหมือนว่าJava จะมีปัญหาโดยเฉพาะใน Windows ตามที่เอกสาร API ระบุว่า

ลักษณะการทำงานหลายประการของวิธีนี้ขึ้นอยู่กับแพลตฟอร์มโดยเนื้อแท้: การดำเนินการเปลี่ยนชื่ออาจไม่สามารถย้ายไฟล์จากระบบไฟล์หนึ่งไปยังอีกระบบหนึ่งอาจไม่ใช่ atomic และอาจไม่สำเร็จหากไฟล์ที่มีชื่อพา ธ นามธรรมปลายทาง มีอยู่แล้ว. ควรตรวจสอบค่าส่งคืนเสมอเพื่อให้แน่ใจว่าการดำเนินการเปลี่ยนชื่อสำเร็จ

ในกรณีของฉันเป็นส่วนหนึ่งของขั้นตอนการอัพเกรดฉันจำเป็นต้องย้าย (เปลี่ยนชื่อ) ไดเร็กทอรีที่อาจมีข้อมูลกิกะไบต์ (ไดเร็กทอรีย่อยจำนวนมากและไฟล์ที่มีขนาดแตกต่างกัน) การย้ายจะกระทำภายในพาร์ติชัน / ไดรฟ์เดียวกันเสมอดังนั้นจึงไม่จำเป็นต้องย้ายไฟล์ทั้งหมดบนดิสก์จริงๆ

มีไม่ควรจะล็อคไฟล์ใด ๆ กับเนื้อหาของ dir ที่จะย้าย แต่ยังคงค่อนข้างบ่อย renameTo () ล้มเหลวในการดำเนินงานและผลตอบแทนที่เป็นเท็จ (ฉันแค่คาดเดาว่าการล็อกไฟล์บางไฟล์อาจหมดอายุใน Windows โดยพลการ)

ขณะนี้ฉันมีวิธีทางเลือกที่ใช้การคัดลอกและลบ แต่นี้ครับเพราะมันอาจจะใช้เวลามากของเวลาขึ้นอยู่กับขนาดของโฟลเดอร์ ฉันยังพิจารณาเพียงแค่บันทึกข้อเท็จจริงที่ว่าผู้ใช้สามารถย้ายโฟลเดอร์ด้วยตนเองเพื่อหลีกเลี่ยงการรอเป็นเวลาหลายชั่วโมง แต่ทางที่ถูกต้องจะเป็นสิ่งที่อัตโนมัติและรวดเร็ว

ดังนั้นคำถามของฉันคือคุณรู้วิธีการอื่นที่เชื่อถือได้ในการย้าย / เปลี่ยนชื่ออย่างรวดเร็วด้วย Java บน Windowsไม่ว่าจะด้วย JDK ธรรมดาหรือไลบรารีภายนอก หรือถ้าคุณรู้วิธีง่ายๆในการตรวจจับและปลดล็อกไฟล์สำหรับโฟลเดอร์ที่กำหนดและเนื้อหาทั้งหมด (อาจเป็นหลายพันไฟล์) ก็ใช้ได้เช่นกัน


แก้ไข : ในกรณีนี้ดูเหมือนว่าเราจะเลิกใช้เพียงแค่renameTo()คำนึงถึงสิ่งต่างๆอีกเล็กน้อย ดูคำตอบนี้


3
คุณสามารถรอ / ใช้ JDK 7 ซึ่งมีการรองรับระบบไฟล์ที่ดีกว่ามาก
akarnokd

@ kd304 ฉันไม่สามารถรอหรือใช้เวอร์ชันระหว่างการพัฒนาได้ แต่น่าสนใจที่จะรู้ว่ากำลังจะเกิดขึ้น
Jonik

คำตอบ:


52

ดูFiles.move()วิธีการใน JDK 7

ตัวอย่าง:

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
น่าเสียดายที่ Java7 ไม่ใช่คำตอบเสมอไป (เช่น 42 คือ)
wuppi

1
แม้แต่ใน Ubuntu, JDK7 เราก็ประสบปัญหานี้เมื่อเรียกใช้โค้ดบน EC2 ด้วยที่เก็บข้อมูล EBS File.renameTo ล้มเหลวดังนั้น File.canWrite
saurabheights

โปรดทราบว่าสิ่งนี้ไม่น่าเชื่อถือเท่ากับ File # renameTo () เพียงแค่ให้ข้อผิดพลาดที่เป็นประโยชน์มากขึ้นเมื่อล้มเหลว วิธีเดียวที่น่าเชื่อถือพอสมควรที่ฉันพบคือคัดลอกไฟล์ด้วยไฟล์ # คัดลอกไปยังชื่อใหม่จากนั้นลบต้นฉบับโดยใช้ Files # delete (ซึ่งการลบเองอาจล้มเหลวด้วยเหตุผลเดียวกัน Files # move อาจล้มเหลว) .
เข้ามาใน

26

สำหรับสิ่งที่คุ้มค่าแนวคิดเพิ่มเติมบางประการ:

  1. ใน Windows renameTo()ดูเหมือนว่าจะล้มเหลวหากมีไดเร็กทอรีเป้าหมายแม้ว่าจะว่างเปล่าก็ตาม สิ่งนี้ทำให้ฉันประหลาดใจเมื่อฉันได้ลองใช้ Linux ซึ่งจะrenameTo()ประสบความสำเร็จหากเป้าหมายมีอยู่ตราบใดที่ยังว่าง

    (เห็นได้ชัดว่าฉันไม่ควรคิดว่าสิ่งนี้ใช้งานได้เหมือนกันในทุกแพลตฟอร์มนี่คือสิ่งที่ Javadoc เตือน)

  2. หากคุณสงสัยว่าอาจมีการล็อกไฟล์ที่ค้างอยู่ให้รอสักครู่ก่อนที่การย้าย / เปลี่ยนชื่ออาจช่วยได้ (ในจุดหนึ่งในโปรแกรมติดตั้ง / ตัวอัปเกรดของเราเราได้เพิ่มการทำงาน "สลีป" และแถบความคืบหน้าที่ไม่แน่นอนเป็นเวลา 10 วินาทีเนื่องจากอาจมีบริการค้างอยู่ในบางไฟล์) บางทีอาจใช้กลไกการลองใหม่ง่ายๆที่ลองrenameTo()แล้วรอสักครู่ (ซึ่งอาจเพิ่มขึ้นทีละน้อย) จนกว่าการดำเนินการจะสำเร็จหรือถึงระยะหมดเวลา

ในกรณีของฉันปัญหาส่วนใหญ่ดูเหมือนจะได้รับการแก้ไขแล้วโดยคำนึงถึงทั้งสองข้อข้างต้นดังนั้นเราจึงไม่จำเป็นต้องทำการเรียกเคอร์เนลแบบเนทีฟหรือบางอย่างในภายหลัง


2
ตอนนี้ฉันยอมรับคำตอบของตัวเองเนื่องจากอธิบายถึงสิ่งที่ช่วยได้ในกรณีของเรา อย่างไรก็ตามหากมีผู้ให้คำตอบที่ดีเยี่ยมสำหรับปัญหาทั่วไปเกี่ยวกับ renameTo () อย่าลังเลที่จะโพสต์และเรายินดีที่จะพิจารณาคำตอบที่ได้รับการยอมรับอีกครั้ง
Jonik

4
6.5 ปีต่อมาฉันคิดว่าถึงเวลาที่ต้องยอมรับคำตอบของJDK 7แทนโดยเฉพาะอย่างยิ่งเมื่อหลาย ๆ คนคิดว่ามันมีประโยชน์ =)
Jonik

19

โพสต์ต้นฉบับร้องขอ "วิธีอื่นที่เชื่อถือได้ในการย้าย / เปลี่ยนชื่ออย่างรวดเร็วด้วย Java บน Windows ไม่ว่าจะด้วย JDK ธรรมดาหรือไลบรารีภายนอกบางส่วน"

อีกตัวเลือกหนึ่งไม่ได้กล่าวถึงที่นี่ยังเป็น v1.3.2 หรือในภายหลังของapache.commons.ioห้องสมุดซึ่งรวมถึงFileUtils.moveFile ()

มันพ่น IOException แทนที่จะส่งคืนบูลีนเท็จเมื่อเกิดข้อผิดพลาด

ดูการตอบสนองของbig lepในหัวข้ออื่น ๆนี้


2
นอกจากนี้ดูเหมือนว่า JDK 1.7 จะมีการรองรับ I / O ของระบบไฟล์ที่ดีกว่า ลองดู java.nio.file.Path.moveTo (): java.sun.com/javase/7/docs/api/java/nio/file/Path.html
MykennaC

2
JDK 1.7 ไม่มีวิธีการใด ๆjava.nio.file.Path.moveTo()
Malte Schwerhoff

5

ในกรณีของฉันดูเหมือนว่าจะเป็นวัตถุที่ตายแล้วในแอปพลิเคชันของฉันเองซึ่งคอยจัดการกับไฟล์นั้น วิธีแก้ปัญหานั้นใช้ได้ผลสำหรับฉัน:

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

ข้อได้เปรียบ: ค่อนข้างรวดเร็วเนื่องจากไม่มี Thread.sleep () ที่มีเวลาฮาร์ดโค้ดเฉพาะ

ข้อเสีย: ขีด จำกัด ที่ 20 คือตัวเลขที่เข้ารหัส ในการทดสอบทั้งหมดของฉัน i = 1 ก็เพียงพอแล้ว แต่เพื่อให้แน่ใจว่าฉันทิ้งไว้ที่ 20


1
ฉันทำสิ่งที่คล้ายกัน แต่ด้วยการนอนหลับ 100ms ในวง
Lawrence Dol

4

ฉันรู้ว่าสิ่งนี้ดูเหมือนจะเป็นการแฮ็คเล็กน้อย แต่สำหรับสิ่งที่ฉันต้องการมันดูเหมือนว่าผู้อ่านและนักเขียนที่บัฟเฟอร์จะไม่มีปัญหาในการสร้างไฟล์

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

}

ทำงานได้ดีกับไฟล์ข้อความขนาดเล็กโดยเป็นส่วนหนึ่งของโปรแกรมแยกวิเคราะห์เพียงตรวจสอบให้แน่ใจว่า oldName และ newName เป็นพา ธ แบบเต็มไปยังตำแหน่งไฟล์

ไชโยแคคตัส


4

โค้ดต่อไปนี้ไม่ใช่ 'ทางเลือก' แต่ใช้ได้กับฉันทั้งในสภาพแวดล้อม Windows และ 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
อืมรหัสนี้จะลบ srcFile แม้ว่า renameTo (หรือ destFile.delete) จะล้มเหลวและเมธอดจะพ่น IOException ฉันไม่แน่ใจว่านั่นเป็นความคิดที่ดีหรือเปล่า
Jonik

1
@Jonik, Thanx, รหัสถาวรที่จะไม่ลบไฟล์ src หากการเปลี่ยนชื่อล้มเหลว
ม้าบ้า

ขอบคุณสำหรับการแบ่งปันสิ่งนี้แก้ไขปัญหาการเปลี่ยนชื่อของฉันบน windows
BillMan

3

บน windows ฉันใช้Runtime.getRuntime().exec("cmd \\c ")แล้วใช้ฟังก์ชันเปลี่ยนชื่อ commandline เพื่อเปลี่ยนชื่อไฟล์จริงๆ มีความยืดหยุ่นมากขึ้นเช่นหากคุณต้องการเปลี่ยนชื่อนามสกุลของไฟล์ txt ทั้งหมดใน dir to bak เพียงแค่เขียนสิ่งนี้ลงในเอาต์พุตสตรีม:

เปลี่ยนชื่อ * .txt * .bak

ฉันรู้ว่ามันไม่ใช่วิธีแก้ปัญหาที่ดี แต่เห็นได้ชัดว่ามันใช้ได้ผลสำหรับฉันเสมอดีกว่ามากจากการรองรับ Java แบบอินไลน์


สุดยอดกว่านี้เยอะ! ขอบคุณ! :-)
gaffcz

2

ทำไมจะไม่ล่ะ....

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 ไม่ทำอะไรเลยหากไม่มีไฟล์ที่มีอยู่ แต่เห็นได้ชัดว่าอาจเป็นเครื่องมือที่ดีกว่าในการแก้ไขปัญหานี้


2

ฉันมีปัญหาที่คล้ายกัน ไฟล์ถูกคัดลอกค่อนข้างย้ายบน Windows แต่ทำงานได้ดีบน Linux ฉันแก้ไขปัญหาโดยการปิด fileInputStream ที่เปิดอยู่ก่อนที่จะเรียก renameTo () ทดสอบบน Windows XP

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

1

ในกรณีของฉันข้อผิดพลาดอยู่ในพา ธ ของไดเร็กทอรีหลัก อาจจะมีข้อผิดพลาดฉันต้องใช้สตริงย่อยเพื่อให้ได้เส้นทางที่ถูกต้อง

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

0

ฉันรู้ว่ามันแย่ แต่อีกทางเลือกหนึ่งคือการสร้างสคริปต์ค้างคาวที่แสดงผลอะไรง่ายๆเช่น "สำเร็จ" หรือ "ข้อผิดพลาด" เรียกใช้รอให้ดำเนินการแล้วตรวจสอบผลลัพธ์

Runtime.getRuntime (). exec ("cmd / c เริ่ม test.bat");

กระทู้นี้อาจจะน่าสนใจ ตรวจสอบคลาส Process เกี่ยวกับวิธีอ่านเอาต์พุตคอนโซลของกระบวนการอื่นด้วย


-2

คุณอาจลองrobocopy นี่ไม่ใช่การ "เปลี่ยนชื่อ" อย่างแน่นอน แต่น่าเชื่อถือมาก

Robocopy ออกแบบมาเพื่อการมิเรอร์ไดเร็กทอรีหรือไดเร็กทอรีทรีที่เชื่อถือได้ มีคุณสมบัติเพื่อให้แน่ใจว่ามีการคัดลอกแอตทริบิวต์และคุณสมบัติของ NTFS ทั้งหมดและรวมถึงรหัสรีสตาร์ทเพิ่มเติมสำหรับการเชื่อมต่อเครือข่ายที่อาจเกิดการหยุดชะงัก


ขอบคุณ. แต่เนื่องจาก robocopy ไม่ใช่ไลบรารี Java จึงคงไม่ง่ายนักที่จะ (รวมกลุ่มและ) ใช้จากรหัส Java ของฉัน ...
Jonik

-2

ในการย้าย / เปลี่ยนชื่อไฟล์คุณสามารถใช้ฟังก์ชันนี้:

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

ถูกกำหนดไว้ใน kernel32.dll


1
ฉันรู้สึกว่าประสบปัญหาในการห่อสิ่งนี้ใน JNI นั้นมากกว่าความพยายามที่ต้องใช้ในการห่อ robocopy ใน Process Decorator
Kevin Montrose

ใช่นี่คือราคาที่คุณจ่ายสำหรับสิ่งที่เป็นนามธรรม - และเมื่อมันรั่วมันก็รั่วดี = D
Chii

ขอบคุณฉันอาจพิจารณาสิ่งนี้หากไม่ซับซ้อนเกินไป ฉันไม่เคยใช้ JNI และไม่พบตัวอย่างที่ดีในการเรียกใช้ฟังก์ชันเคอร์เนลของ Windows บน SO ฉันจึงโพสต์คำถามนี้: stackoverflow.com/questions/1000723/…
Jonik

คุณสามารถลองใช้กระดาษห่อ JNI ทั่วไปเช่นjohannburkard.de/software/nativecallเนื่องจากเป็นการเรียกใช้ฟังก์ชันที่ค่อนข้างง่าย
Peter Smith

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

ข้างต้นเป็นรหัสง่ายๆ ฉันได้ทดสอบบน windows 7 และใช้งานได้ดีอย่างสมบูรณ์


11
มีหลายกรณีที่ renameTo () ทำงานไม่น่าเชื่อถือ นั่นคือประเด็นทั้งหมดของคำถาม
Jonik
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.