我一直在尝试使用FileLock
获取对文件的独占访问权限,以便:
因为在Windows上(至少),您似乎无法删除,重命名或写入已在使用的文件。我写的代码看起来像这样:
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;
}
以下是一些演示此问题的单元测试。您需要在类路径上安装Apache commons-io来运行第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);
}
}
没有一项测试通过。前2个失败,最后一个抛出此异常:
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)
似乎lock()
方法锁定文件,然后阻止我重命名/删除/写入文件。我的假设是锁定文件会让我对该文件进行独占访问,因此我可以重命名/删除/编写它,而不必担心其他任何进程是否也在访问它。
要么我误解了FileLock
,要么它不适合解决我的问题。
答案 0 :(得分:16)
有关其他进程的消息仅表示系统上的某些进程已打开该文件。它实际上并不检查该进程是否与尝试删除/重命名文件的进程相同。在这种情况下,同一程序打开了文件。你打开它来获得锁定。这里的锁几乎没有价值,特别是如果你这样做是为了删除或重命名操作。
要执行您想要的操作,您需要锁定目录条目。这在Java中不可用,可能在Windows中不可用。这些(删除和插入)操作是原子操作。这意味着操作系统负责为您锁定目录和其他文件系统结构。如果另一个进程(或您自己的)打开了文件,则这些操作将失败。如果您尝试以独占方式锁定文件(目录条目)而另一个进程(或您自己的)将文件打开,则锁定将失败。没有区别,但尝试执行锁定只会使操作变得复杂,并且在这种情况下,使操作无法进行(即,在尝试执行操作之前始终打开文件)。
现在写入文件是一个有效的锁定操作。锁定要写入的文件或文件的一部分,然后它将起作用。在Windows上,此锁定机制是必需的,因此另一个打开/文件描述符将无法写入锁定下的任何部分。
修改的
根据FileChannel.lock
上的JavaDoc,它与调用FileChannel.lock(0L, Long.MAXVALUE, false)
相同。这是从第一个字节到最后一个字节的区域的独占锁。
其次,根据FileLock
上的JavaDoc
锁是否实际阻止其他程序访问锁定区域的内容是系统相关的,因此未指定。某些系统的本机文件锁定功能仅仅是建议性的,这意味着程序必须协作地观察已知的锁定协议以保证数据的完整性。在其他系统上,本机文件锁是必需的,这意味着如果一个程序锁定文件的某个区域,则实际上阻止其他程序以违反该锁的方式访问该区域。在其他系统上,可以基于每个文件配置本机文件锁是建议性的还是必需的。为了确保跨平台的一致和正确的行为,强烈建议使用此API提供的锁,就好像它们是咨询锁一样。
修改的
对于testWrite
方法。公共I / O静态方法上的JavaDoc是稀疏的,但是说“如果文件不存在,则将字符串写入创建文件的文件......”并且正如此方法需要File
而不是打开的流,它可能会在内部打开文件。可能它没有打开具有共享访问权限的文件,也没有打开附加访问权限。这意味着现有的open和lock(你打开以获取锁定的通道)阻止了这种使用。要了解更多,您需要获取该方法的源代码并查看它正在做什么。
修改的
抱歉,我的立场得到了纠正。我检查了Windows API,Windows上必须进行文件锁定。这就是写入失败的原因。第一次打开(您的new RandomAccessFile
)和锁定文件被锁定。打开写入字符串成功但写入失败,因为另一个打开(文件描述符)具有强制独占锁定下的文件的完整范围 - 也就是说,在释放锁定之前,没有其他文件描述符可以写入文件。
请注意,锁定与文件描述符 NOT 进程或线程相关联。
答案 1 :(得分:1)
您所使用的锁是locking a region inside a file,而不是文件本身,因此当区域被锁定时,您无法删除或重命名该文件。
您可能需要查看Commons Transaction项目。
答案 2 :(得分:1)
delete
和rename
操作由操作系统执行,并且是原子操作(在大多数操作系统上),因此不需要锁定。
要将字符串写入文件,首先写入临时文件(例如foo.tmp)会更简单,然后在准备就绪后重命名。
答案 3 :(得分:0)
指定Java文件锁只是为了防止其他锁,而不是其他锁。它们在特定平台上的行为,即任何额外的语义,都是特定于平台的。
答案 4 :(得分:0)
在执行重命名或删除等操作之前,您应该使用方法release()释放文件。