2015-07-24 16 views
18

Hãy xem xét ví dụ sau lớp Java (pom.xml dưới đây):hành vi Odd khi xóa tập tin với Files.delete()

package test.filedelete; 

import java.io.ByteArrayInputStream; 
import java.io.IOException; 
import java.io.InputStream; 
import java.io.OutputStream; 
import java.nio.file.Files; 
import java.nio.file.NoSuchFileException; 
import java.nio.file.Path; 

import org.apache.commons.io.IOUtils; 

public class Main 
{ 
    public static void main(String[] args) throws IOException 
    { 
     byte[] bytes = "testtesttesttesttesttesttesttesttesttest".getBytes(); 
     InputStream is = new ByteArrayInputStream(bytes); 

     Path tempFileToBeDeleted = Files.createTempFile("test", ""); 
     OutputStream os = Files.newOutputStream(tempFileToBeDeleted); 
     IOUtils.copy(is, os); 

     deleteAndCheck(tempFileToBeDeleted); 

     // breakpoint 1 
     System.out.println("\nClosing stream\n"); 

     os.close(); 

     deleteAndCheck(tempFileToBeDeleted); 
    } 

    private static void deleteAndCheck(Path file) throws IOException 
    { 
     System.out.println("Deleting file: " + file); 
     try 
     { 
      Files.delete(file); 
     } 
     catch (NoSuchFileException e) 
     { 
      System.out.println("No such file"); 
     } 
     System.out.println("File really deleted: " + !Files.exists(file)); 

     System.out.println("Recreating deleted file ..."); 
     try 
     { 
      Files.createFile(file); 
      System.out.println("Recreation successful"); 
     } 
     catch (IOException e) 
     { 
      System.out.println("Recreation not possible, exception: " + e.getClass().getName()); 
     } 
    } 
} 

Tôi viết thư cho một FileOutputStream và cố gắng xóa các tập tin sau đó mà không đóng cửa Luồng đầu tiên. Đây là vấn đề ban đầu của tôi, và tất nhiên là sai, nhưng nó dẫn đến một số quan sát kỳ lạ.

Khi bạn chạy các phương pháp chính trên Windows 7 nó tạo ra kết quả như sau:

Deleting file: C:\Users\MSCHAE~1\AppData\Local\Temp\test6100073603559201768 
File really deleted: true 
Recreating deleted file ... 
Recreation not possible, exception: java.nio.file.AccessDeniedException 

Closing stream 

Deleting file: C:\Users\MSCHAE~1\AppData\Local\Temp\test6100073603559201768 
No such file 
File really deleted: true 
Recreating deleted file ... 
Recreation successful 
  • Tại sao cuộc gọi đầu tiên để Files.delete() không ném một ngoại lệ?
  • Tại sao cuộc gọi sau đây đến Files.exist() trả về false?
  • Tại sao không thể tạo tệp mới?

Về câu hỏi cuối cùng tôi nhận thấy rằng tệp vẫn hiển thị trong Explorer khi bạn dừng tại điểm ngắt 1. Khi bạn chấm dứt JVM, tệp sẽ bị xóa. Sau khi đóng luồng deleteAndCheck() hoạt động như mong đợi.

Dường như với tôi rằng việc xóa không được truyền sang HĐH trước khi đóng luồng và API tệp không phản ánh đúng cách.

Ai đó có thể giải thích chính xác những gì đang xảy ra ở đây?

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 
    <modelVersion>4.0.0</modelVersion> 
    <groupId>test</groupId> 
    <artifactId>filedelete</artifactId> 
    <version>0.0.1-SNAPSHOT</version> 

    <dependencies> 
     <dependency> 
      <groupId>commons-io</groupId> 
      <artifactId>commons-io</artifactId> 
      <version>2.4</version> 
     </dependency> 
    </dependencies> 
</project> 

Cập nhật để làm rõ

Các tập tin sẽ biến mất trong Windows explorer, nếu dòng được đóng lại và Files.delete() được gọi - hoạt động cuối cùng trình kích hoạt - hoặc nếu Files.delete() đã được gọi mà không đóng luồng và JVM bị chấm dứt.

+0

Hệ điều hành là gì? Nếu giống Unix, bạn có thể thử và in các quyền của thư mục mẹ không? – fge

+1

Và bằng cách này, bạn có thể trực tiếp sử dụng 'Files.copy()' để sao chép nội dung của 'InputStream' vào một' Path'; không cần IOUtils – fge

+0

Thử đặt điểm ngắt trên 'Files.delete (file);' và sau đó bước qua nó. Tệp có thực sự biến mất khỏi cửa sổ trình khám phá tại thời điểm đó không? Tôi nghi ngờ rằng bằng cách nào đó xóa thành công, nhưng cửa sổ đang giữ tập tin vì có một tham chiếu khác mở cho nó. Điều này có thể là một cái gì đó thấp cấp trong cách cửa sổ hoạt động. Tôi sẽ không mong đợi như vậy trên một hệ thống Posix (ví dụ: Linux). –

Trả lời

17

Bạn có thể xóa tệp đang mở không?

Hoàn toàn hợp lệ để xóa mục nhập thư mục của tệp khi tệp được mở. Trong Unix, đây là ngữ nghĩa mặc định và Windows hoạt động tương tự miễn là FILE_SHARE_DELETE được đặt trên tất cả các tệp xử lý mở cho tệp đó.

[Edit: Nhờ @couling cho các cuộc thảo luận và chỉnh sửa]

Tuy nhiên, có một khác biệt nhỏ: Unix xóa các tập tin tên ngay, trong khi Windows xóa tên tập tin chỉ khi cuối cùng xử lý được đóng. Tuy nhiên, nó ngăn bạn mở một tệp có cùng tên cho đến khi xử lý cuối cùng cho tệp (đã xóa) bị đóng.

Go hình ...

Trên cả hai hệ thống, tuy nhiên, xóa các tập tin không nhất thiết phải làm cho các tập tin đi, nó vẫn chiếm không gian trên đĩa chừng nào vẫn còn là một tay cầm mở cửa cho nó. Không gian bị chiếm bởi tập tin chỉ được phát hành khi chốt mở cuối cùng được đóng lại.

Excursion: Windows

Đó nó là cần thiết để xác định lá cờ trên Windows làm cho nó dường như hầu hết mọi người rằng Windows không thể xóa các tập tin mở, nhưng đó là thực tế không đúng sự thật. Đó chỉ là hành vi mặc định .

Tình hình là không thực sự được cải thiện bởi thực tế là tài liệu Windows API dường như mơ hồ về quy trình (cố tình?):

CreateFile():

phép hoạt động mở tiếp theo trên một tập tin hoặc điện thoại để yêu cầu xóa quyền truy cập.

Nếu không, các quy trình khác không thể mở tệp hoặc thiết bị nếu chúng yêu cầu quyền truy cập xóa.

Nếu cờ này không được chỉ định, nhưng tệp hoặc thiết bị đã được mở để xóa quyền truy cập, chức năng sẽ không thành công. Lưu ý Xóa quyền truy cập cho phép cả thao tác xóa và đổi tên.

DeleteFile():

Chức năng DeleteFile đánh dấu một tập tin để xóa trên gần. Do đó, việc xóa tệp sẽ không xảy ra cho đến khi xử lý cuối cùng cho tệp được đóng. Các cuộc gọi tiếp theo tới CreateFile để mở tệp không thành công với ERROR_ACCESS_DENIED.

Có một chốt mở đối với tệp không có tên là một trong những phương pháp điển hình nhất để tạo tệp tạm thời chưa được đặt tên: Tạo tệp mới, mở tệp, xóa tệp. Bây giờ bạn có một xử lý cho một tập tin mà không ai khác có thể mở. Trên Unix, tên tệp thực sự biến mất và trên Windows bạn không thể mở tệp có cùng tên.

Câu hỏi đặt ra bây giờ là:

Liệu Files.newOutputStream() thiết FILE_SHARE_DELETE?

Nhìn vào the source, bạn có thể thấy rằng shareDelete thực sự mặc định là true. Cách duy nhất để đặt lại là sử dụng ExtendedOpenOptionNOSHARE_DELETE không chuẩn.

Vì vậy, có, bạn có thể xóa các tệp đã mở trong Java trừ khi chúng được khóa rõ ràng.

Tại sao tôi không thể tạo lại tệp đã xóa?

Câu trả lời cho câu hỏi đó được ẩn trong tài liệu của DeleteFile() ở trên: Tệp chỉ được đánh dấu để xóa, tệp vẫn ở đó.Trên Windows, bạn không thể tạo tệp có tên của tệp được đánh dấu để xóa cho đến khi tệp bị xóa đúng cách, nghĩa là tất cả các thao tác xử lý tệp đều bị đóng.

Sự nhầm lẫn có thể xảy ra khi xóa tên trộn và xóa tệp thực sự có thể là lý do tại sao Windows không cho phép xóa tệp đang mở theo mặc định ngay từ đầu.

Tại sao Files.exists() trả lại false?

Files.exists() vào cuối sâu trên Windows mở tập tin đó tại một số điểm và chúng tôi đã biết rằng chúng ta không thể mở lại một tập tin đã xóa-nhưng-vẫn-mở trên Windows.

Cụ thể: Các cuộc gọi mã Java FileSystemProvider.checkAccess()) không có đối số, gọi WindowsFileSystemProvider.checkReadAccess() mà ngay lập tức cố gắng mở tệp và do đó không thành công. Từ những gì tôi có thể nói, đây là đường dẫn khi bạn gọi Files.exist().

Ngoài ra còn có một đường dẫn mã khác gọi số GetFileAttributeEx() để truy xuất thuộc tính tệp. Một lần nữa, nó không được ghi lại những gì xảy ra khi bạn cố gắng truy xuất các thuộc tính của tệp bị xóa nhưng chưa bị xóa, nhưng thực sự, bạn không thể truy xuất thuộc tính tệp của tệp được đánh dấu để xóa.

Đoán, tôi muốn nói rằng GetFileAttributeEx() gọi GetFileInformationByHandle() tại một thời điểm nào đó mà nó sẽ không bao giờ nhận được vì không thể xử lý tệp ngay từ đầu.

Vì vậy, thực sự, sau DeleteFile() tệp đã biến mất cho hầu hết các mục đích thực tế. Nó vẫn có một tên, tuy nhiên, xuất hiện trong danh sách thư mục và bạn không thể mở một tệp có cùng tên cho đến khi tệp gốc có tất cả các xử lý của nó được đóng lại.

Hành vi này là nhiều hay ít phù hợp, bởi vì sử dụng GetFileAttributes() để kiểm tra xem một tập tin tồn tại là một thực sự là một tập khả năng tiếp cận kiểm tra, được hiểu là tập tin tồn tại. FindFirstFile() (được sử dụng bởi Windows Explorer để xác định danh sách tệp) tìm thấy tệp tên nhưng không cho bạn biết gì về số trợ năng của tên.

Chào mừng bạn đến với một vài vòng lặp lạ trong đầu của bạn.

+0

Tôi không bị thuyết phục bởi các lập luận của bạn về việc triển khai Windows ở đây và sự giống nhau của nó với Linux. Tài liệu tham khảo thủ công của bạn là về các quyền mở không cho dù hoạt động xóa thực tế có thành công hay không. Tôi cần phải xem một ví dụ mức thấp (viết bằng C hoặc tương tự) để chứng minh trường hợp. Thông thường các cửa sổ có liên kết mạnh hơn giữa tên và tệp so với các hệ thống POSIX. Các tập tin thường không tồn tại trên các cửa sổ nếu tên không. –

+0

@couling Trước khi tôi chứng minh một ấm trà, bạn nghĩ 'FILE_SHARE_DELETE' là gì? – dhke

+0

lỗi của sách hướng dẫn không rõ ràng. Xin lỗi, tôi là người theo dõi, nhưng những gì nó nói là 'Nếu không, các quy trình khác không thể mở tệp hoặc thiết bị nếu họ yêu cầu xóa quyền truy cập.' Điều này nói về chức năng" mở "không phải là chức năng" xóa "và không nói rõ ràng việc xóa có thể xảy ra trong khi tệp được mở ở nơi khác. –

2

Nếu Files.delete không ném ngoại lệ, điều đó có nghĩa là nó đã xóa tệp. Files.delete javadoc nói rằng "trên một số hệ điều hành, nó có thể không thể loại bỏ một tệp khi nó được mở và được sử dụng bởi máy ảo Java này hoặc các chương trình khác".

+0

Nhưng nó không nên ném một IO/Access ngoại lệ sau đó? - Ý tôi là bây giờ nó nói 'okay, xóa nó', mà không thực sự xóa nó. Việc xóa đang xảy ra ngay khi đầu ra được đóng. Vì vậy, bạn về mặt lý thuyết có thể tiếp tục nội dung ghi mà không biết rằng một xóa sẽ xảy ra ở cuối ... – dognose

+2

@dognose Bởi vì các tập tin * Tên * đã biến mất, ví dụ: xóa đã thành công.Tuy nhiên, tệp vẫn tồn tại (cho đến khi bạn đóng chốt cuối cùng cho nó). Các ứng dụng Windows thường khiến bạn tin rằng bạn không thể xóa các tệp đang mở, nhưng điều đó thực sự không đúng. – dhke

Các vấn đề liên quan