我正在开发一个Java进程,该进程应该有效地(递归地)将文件/目录从源位置复制到目标位置。
要做到这一点,我想:
- 创建锁
- 如果目标文件不存在,请复制它
- 否则,如果目标文件不同,请复制它
- 否则它们是一样的,所以什么都不做
- 松开锁
为了检查内容是否相等,我计划使用Apache Commons IO FileUtils
方法contentsEqual(...)
。为了进行复制,我计划使用Apache Commons IO FileUtils
方法copyFile(...)
。
所以,我想到的代码是(这只是针对文件,目录是递归处理的,直到文件的方法):
private static void checkAndUpdateFile(File src, File dest) throws IOException {
FileOutputStream out = new FileOutputStream(dest);
FileChannel channel = out.getChannel();
FileLock lock = channel.lock();
if (!dest.exists()) {
FileUtils.copyFile(src, out);
} else if (!FileUtils.contentEquals(src, dest)) {
FileUtils.copyFile(src, out);
}
lock.release();
channel.close();
out.close();
}
这样可以锁定文件(很棒),并复制文件(超级)。
但是,无论何时复制文件,它都会将复制文件的上次修改时间戳设置为复制时间。这意味着对FileUtils.contentEquals(src, dest)
的后续调用将继续返回false
,因此文件将被重新复制。
我真正想要的类似于FileUtils.copyFile(src, dest, true)
,它保留了文件时间戳,并且通过调用FileUtils.contentEquals(src, dest)
可以实现。这将要求锁定在File
上,而不是FileOutputStream
上,否则对FileUtils.copyFile(src, dest, true)
的调用将失败并引发异常,因为文件已锁定。
或者,我考虑做FileUtils.copyFile(src, dest, true)
方法所做的事情,即调用dest.setLastModified(src.lastModified())
。然而,这必须在锁释放后调用,如果同一进程同时执行多次,这可能会导致问题。
我也考虑过在源文件上加锁的ides,但这没有帮助,因为我必须把它放在FileInputStream
上,并且我想把File
传递给FileUtils.copyFile(src, dest)
。
因此:
- 有什么更简单的方法可以实现我想要做的事情吗
- 是否可以锁定文件,而不是文件的派生文件
- 解决这个问题的最佳方法是什么?
- 也就是说,我只需要为其中的一部分编写自己的方法吗?(可能是
copyFile(...)
)
- 也就是说,我只需要为其中的一部分编写自己的方法吗?(可能是
您可以使用Guava Google Core Library
来比较两个文件。
作为字节源
ByteSource文档
ByteSource inByte = Resources.asByteSource(srcFileURL);
ByteSource outByte = Files.asByteSource(srcFileURL2);
boolean fileEquals= inByte.contentEquals(outByte));
所以。。。最后,我采用了编写自己的copy()
和compare()
方法来使用已锁定的FileChannel
对象的方法。以下是我提出的解决方案——尽管我预计其他人可能会提出改进建议。这是通过查看apache.commons.io类FileUtils
和IOUtils
的源代码得到的。
private static final int s_eof = -1;
private static final int s_byteBuffer = 10240;
private static void checkAndUpdateFile(File src, File dest) throws IOException {
FileInputStream in = new FileInputStream(src);
FileChannel srcChannel = in.getChannel();
FileChannel destChannel = null;
FileLock destLock = null;
try {
if (!dest.exists()) {
final RandomAccessFile destFile = new RandomAccessFile(dest, "rw");
destChannel = destFile.getChannel();
destLock = destChannel.lock();
copyFileChannels(srcChannel, destChannel);
dest.setLastModified(src.lastModified());
} else {
final RandomAccessFile destFile = new RandomAccessFile(dest, "rw");
destChannel = destFile.getChannel();
destLock = destChannel.lock();
if (!compareFileChannels(srcChannel, destChannel)) {
copyFileChannels(srcChannel, destChannel);
dest.setLastModified(src.lastModified());
}
}
} finally {
if (destLock != null) {
destLock.release();
}
if (destChannel != null) {
destChannel.close();
}
srcChannel.close();
in.close();
}
}
protected static void copyFileChannels(FileChannel src,
FileChannel dest) throws IOException {
final long size = src.size();
for (long pos = 0; pos < size; ) {
long count =
((size - pos) > s_byteBuffer) ? s_byteBuffer : (size - pos);
pos += dest.transferFrom(src, pos, count);
}
}
protected static boolean compareFileChannels(FileChannel a,
FileChannel b) throws IOException {
if (a.size() != b.size()) {
return false;
} else {
final ByteBuffer aBuffer = ByteBuffer.allocate(s_byteBuffer);
final ByteBuffer bBuffer = ByteBuffer.allocate(s_byteBuffer);
for (int aCh = a.read(aBuffer); s_eof != aCh; ) {
int bCh = b.read(bBuffer);
if (aCh != bCh || aBuffer.compareTo(bBuffer) != 0) {
return false;
}
aBuffer.clear();
aCh = a.read(aBuffer);
bBuffer.clear();
}
return s_eof == b.read(bBuffer);
}
}