2010-10-26 35 views
13

Tôi đã cố gắng sử dụng FileLock để có được truy cập độc quyền vào một tập tin để:FileLock hoạt động như thế nào?

  • xóa nó
  • đổi tên nó
  • ghi vào nó

Bởi vì trên Windows (ít nhất) có vẻ như bạn không thể xóa, đổi tên hoặc ghi vào một tệp đã được sử dụng. Mã tôi đã viết trông giống như sau:

import java.io.File; 
import java.io.FileNotFoundException; 
import java.io.IOException; 
import java.io.RandomAccessFile; 
import java.nio.channels.FileChannel; 
import java.nio.channels.FileLock; 

public abstract class LockedFileOperation { 

    public void execute(File file) throws IOException { 

     if (!file.exists()) { 
      throw new FileNotFoundException(file.getAbsolutePath()); 
     } 

     FileChannel channel = new RandomAccessFile(file, "rw").getChannel(); 

     try { 
      // Get an exclusive lock on the whole file 
      FileLock lock = channel.lock(); 

      try { 
       doWithLockedFile(file); 
      } finally { 
       lock.release(); 
      } 
     } finally { 
      channel.close(); 
     } 
    } 

    public abstract void doWithLockedFile(File file) throws IOException; 
} 

Dưới đây là một số kiểm tra đơn vị chứng minh sự cố. Bạn sẽ cần phải có Apache commons-io trên classpath của bạn để chạy thử nghiệm thứ 3.

import java.io.File; 
import java.io.IOException; 

import junit.framework.TestCase; 

public class LockedFileOperationTest extends TestCase { 

    private File testFile; 

    @Override 
    protected void setUp() throws Exception { 

     String tmpDir = System.getProperty("java.io.tmpdir"); 
     testFile = new File(tmpDir, "test.tmp"); 

     if (!testFile.exists() && !testFile.createNewFile()) { 
      throw new IOException("Failed to create test file: " + testFile); 
     } 
    } 

    public void testRename() throws IOException { 
     new LockedFileOperation() { 

      @Override 
      public void doWithLockedFile(File file) throws IOException { 
       if (!file.renameTo(new File("C:/Temp/foo"))) { 
        fail(); 
       } 
      } 
     }.execute(testFile); 
    } 

    public void testDelete() throws IOException { 
     new LockedFileOperation() { 

      @Override 
      public void doWithLockedFile(File file) throws IOException { 
       if (!file.delete()) { 
        fail(); 
       } 
      } 
     }.execute(testFile); 
    } 

    public void testWrite() throws IOException { 
     new LockedFileOperation() { 

      @Override 
      public void doWithLockedFile(File file) throws IOException { 
       org.apache.commons.io.FileUtils.writeStringToFile(file, "file content"); 
      } 
     }.execute(testFile); 
    } 
} 

Không có bài kiểm tra nào vượt qua. 2 đầu tiên thất bại, và ném cuối cùng ngoại lệ này:

java.io.IOException: The process cannot access the file because another process has locked a portion of the file 
    at java.io.FileOutputStream.writeBytes(Native Method) 
    at java.io.FileOutputStream.write(FileOutputStream.java:247) 
    at org.apache.commons.io.IOUtils.write(IOUtils.java:784) 
    at org.apache.commons.io.IOUtils.write(IOUtils.java:808) 
    at org.apache.commons.io.FileUtils.writeStringToFile(FileUtils.java:1251) 
    at org.apache.commons.io.FileUtils.writeStringToFile(FileUtils.java:1265) 

Nó có vẻ như phương pháp lock() đặt một khóa trên các tập tin mà sau đó ngăn cản tôi từ đổi tên/xóa/viết nó. Giả định của tôi là khóa tệp sẽ cho tôi quyền truy cập độc quyền vào tệp, vì vậy tôi có thể đổi tên/xóa/ghi nó mà không phải lo lắng về việc liệu có quá trình nào khác cũng đang truy cập vào tệp hay không.

Hoặc tôi là sự hiểu lầm FileLock hoặc đó không phải là giải pháp thích hợp cho vấn đề của tôi.

+0

Vui lòng đăng theo dõi ngăn xếp và kiểm tra đơn vị. – Woot4Moo

+0

Tôi đã cập nhật câu hỏi để bao gồm kiểm tra đơn vị và theo dõi ngăn xếp –

+0

Bạn có thể khóa trên một số tệp "khác" (như tệp kiểm soát) trong khi bạn xóa tệp này ... – rogerdpack

Trả lời

1

Khóa bạn đã chọn là locking a region inside a file, nhưng không phải là tệp, vì vậy trong khi khu vực bị khóa, bạn không thể xóa hoặc đổi tên tệp.

Bạn có thể muốn xem dự án Commons Transaction.

+0

Có, 'lock()' là giống nhau - nó gọi phương thức mà Eugene liên kết tới. Xem JavaDoc mà tôi cũng tham chiếu trong câu trả lời của tôi. –

1

Các hoạt động deleterename được hệ điều hành thực hiện và là nguyên tử (trên hầu hết các hệ điều hành), do đó không cần khóa.

Để viết chuỗi vào tệp, sẽ đơn giản hơn để ghi vào tệp tạm thời trước (ví dụ: foo.tmp) và sau đó đổi tên nó khi đã sẵn sàng.

+0

Tôi cần khóa tệp vì trên Windows bạn không thể khóa, xóa hoặc đổi tên tệp nếu nó đã được sử dụng –

+1

@Don, tệp được mở để gọi 'lock()' nên bạn nghĩ gì về việc xóa/đổi tên sẽ làm? Trên Windows cùng một quá trình có tệp mở sẽ chặn các hoạt động này. Điều gì là sai với việc sử dụng xóa/đổi tên trực tiếp? Nếu một quá trình khác có tệp mở, bạn không thể khóa tệp (theo cách bạn muốn). Và khóa tập tin và sau đó xóa nó là không tốt hơn sau đó chỉ cần xóa nó trong điều khoản của việc giữ các quy trình khác từ truy cập vào tập tin. –

0

Khóa tệp Java chỉ được chỉ định để bảo vệ khỏi các khóa khác và không có gì khác. Cách chúng hoạt động trên các nền tảng cụ thể, nghĩa là bất kỳ ngữ nghĩa bổ sung nào, là nền tảng cụ thể.

+0

Nhưng điều đó không giải thích tại sao các bài kiểm tra đơn vị được hiển thị ở trên thất bại –

+0

Những gì bạn đã nói về chúng chỉ bảo vệ chống lại các khóa khác mâu thuẫn với những gì được viết ở đây http://www.linuxtopia.org/online_books/programming_books/thinking_in_java/TIJ314_030.htm "Khóa tệp được hiển thị cho các quy trình hệ điều hành khác vì tệp Java khóa bản đồ trực tiếp vào cơ sở khóa hệ điều hành gốc". –

+0

Không, nó không mâu thuẫn chút nào. Tôi không nói gì về khả năng hiển thị khóa cho các quá trình khác, hoặc những gì các khóa bản đồ, vì vậy không có gì là mâu thuẫn với báo giá của bạn. – EJP

16

Thông báo về quy trình khác chỉ có nghĩa là một số quy trình trên hệ thống của bạn có tệp mở. Nó không thực sự kiểm tra xem quá trình đó có xảy ra giống như tiến trình xóa/đổi tên tệp hay không. Trong trường hợp này, cùng một chương trình đã mở tệp. Bạn đã mở nó để lấy khóa. Khóa ở đây có ít hoặc không có giá trị, đặc biệt là nếu bạn đang làm điều này để xóa hoặc đổi tên các hoạt động.

Để thực hiện những gì bạn muốn, bạn cần khóa mục nhập thư mục. Điều này không có sẵn trong Java và có thể không có sẵn trong Windows. Các hoạt động (xóa và chèn) này là nguyên tử. Điều đó có nghĩa là hệ điều hành sẽ chăm sóc khóa thư mục và các cấu trúc hệ thống tệp khác cho bạn. Nếu một quá trình khác (hoặc của riêng bạn) có tệp mở thì các hoạt động này sẽ không thành công. Nếu bạn đang cố gắng khóa tập tin độc quyền (mục nhập thư mục) và một tiến trình khác (hoặc của riêng bạn) có tệp đang mở, thì khóa sẽ không thành công.Không có sự khác biệt, nhưng cố gắng để làm khóa chỉ phức tạp, và trong trường hợp này, làm cho các hoạt động không thể (có nghĩa là, các tập tin luôn luôn mở trước khi bạn cố gắng để làm các hoạt động).

Hiện đang ghi vào tệp là thao tác khóa hợp lệ. Khóa tệp hoặc phần của tệp mà bạn muốn ghi vào và sau đó nó sẽ hoạt động. Trên Windows, cơ chế khóa này là bắt buộc nên một bộ mô tả mở/tệp khác sẽ không thể ghi vào bất kỳ phần nào nằm dưới khóa.

EDIT

Theo javadoc trên FileChannel.lock, nó cũng giống như gọi FileChannel.lock(0L, Long.MAXVALUE, false). Đây là một khóa độc quyền trên một vùng từ byte đầu tiên đến byte cuối cùng.

Thứ hai, theo javadoc trên FileLock

hay không một khóa thực sự ngăn cản chương trình khác truy cập vào nội dung của các khu vực bị khóa là hệ thống phụ thuộc và do đó không xác định. Các cơ sở khóa tập tin gốc của một số hệ thống chỉ đơn thuần là tư vấn, có nghĩa là các chương trình phải hợp tác quan sát một giao thức khóa đã biết để đảm bảo tính toàn vẹn của dữ liệu. Trên các hệ thống khóa tập tin gốc khác là bắt buộc, có nghĩa là nếu một chương trình khóa một vùng của một tập tin thì các chương trình khác thực sự bị ngăn cản truy cập vùng đó theo cách sẽ vi phạm khóa. Trên các hệ thống khác, cho dù khóa tệp gốc là tư vấn hay bắt buộc có thể định cấu hình trên cơ sở từng tệp. Để đảm bảo hành vi nhất quán và chính xác trên các nền tảng, khuyên rằng các khóa được cung cấp bởi API này được sử dụng như thể chúng là khóa cố định.

EDIT

Đối với phương pháp testWrite. JavaDoc trên commons I/O static method là thưa thớt nhưng nói "Write a String vào một tập tin tạo tập tin nếu nó không tồn tại .." và khi phương thức này mất File thay vì một dòng đã mở, nó có khả năng mở tập tin nội bộ. Có lẽ nó không phải là mở tập tin với quyền truy cập chia sẻ và cũng mở để truy cập chắp thêm. Điều này có nghĩa là mở và khóa hiện tại (mở của bạn để có được kênh mà từ đó để có được khóa) đang chặn sử dụng đó. Để hiểu nhiều hơn, bạn sẽ cần lấy nguồn cho phương thức đó và nhìn vào những gì nó đang làm.

EDIT

Xin lỗi, tôi đứng sửa chữa. Tôi đã kiểm tra API Windows và khóa tệp là bắt buộc trên Windows. Đây là lý do tại sao viết không thành công. Mở khóa đầu tiên (new RandomAccessFile) và khóa của bạn đã khóa tệp. Mở để ghi chuỗi thành công nhưng ghi không thành công vì một bộ mô tả tệp mở khác có toàn bộ tệp trong khóa độc quyền bắt buộc - nghĩa là, không có bộ mô tả tệp nào có thể ghi vào tệp cho đến khi khóa được giải phóng.

Lưu ý rằng khóa được liên kết với trình mô tả tệp NOT quy trình hoặc chuỗi.

+0

Giải thích tuyệt vời. Cảm ơn rất nhiều cho tất cả những nỗ lực bạn đưa vào này. Tuy nhiên, câu trả lời của bạn vẫn không giải thích tại sao thử nghiệm đơn vị 'testWrite' thất bại, bất kỳ suy nghĩ nào về điều đó? –

+0

@Don, lý do là quá trình của bạn có tệp được mở để lấy khóa (như tôi đã nêu trong phần giải thích của tôi). Cụ thể, mã mới 'RandomAccessFile (tệp, "rw") 'mở tệp. –

+0

@ Rất tiếc, bạn chỉ hỏi về một phương pháp trong thử nghiệm đơn vị của mình. Hãy để tôi xem xét điều đó, nó phụ thuộc vào những gì lớp khác đang làm, đó là Java không chuẩn. –

0

Bạn nên phát hành tệp với bản phát hành phương thức() trước khi thực hiện bất kỳ hành động nào như đổi tên hoặc xóa hoặc ....

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