在一个需求中,我需要将多个文件从一个位置复制到另一个网络位置。
假设我在/src位置有以下文件。
a.pdf, b.pdf, a.doc, b.doc, a.txt and b.txt
我需要立即将a.pdf, a.doc and a.txt文件atomically复制到/dest位置。
目前,我正在使用Java.nio.file.Files包和代码,如下所示
Path srcFile1 = Paths.get("/src/a.pdf");
Path destFile1 = Paths.get("/dest/a.pdf");
Path srcFile2 = Paths.get("/src/a.doc");
Path destFile2 = Paths.get("/dest/a.doc");
Path srcFile3 = Paths.get("/src/a.txt");
Path destFile3 = Paths.get("/dest/a.txt");
Files.copy(srcFile1, destFile1);
Files.copy(srcFile2, destFile2);
Files.copy(srcFile3, destFile3);但在这个过程中,文件会被一个接一个地复制。
作为替代,为了使整个过程成为原子的,我正在考虑压缩所有的文件,并移动到/dest并解压缩到目的地。
这种方法使整个复制过程成为原子是正确的吗?任何一个人都经历过类似的概念并解决了它。
发布于 2020-09-24 12:55:35
这种方法使整个复制过程成为原子是正确的吗?任何一个人都经历过类似的概念并解决了它。
您可以将文件复制到新的临时目录,然后重命名该目录。
在重命名临时目录之前,您需要删除目标目录
如果目标目录中已存在您不想覆盖的其他文件,则可以将临时目录中的所有文件移动到目标目录中。
然而,这并不完全是原子的。
通过删除/dest:
String tmpPath="/tmp/in/same/partition/as/source";
File tmp=new File(tmpPath);
tmp.mkdirs();
Path srcFile1 = Paths.get("/src/a.pdf");
Path destFile1 = Paths.get(tmpPath+"/dest/a.pdf");
Path srcFile2 = Paths.get("/src/a.doc");
Path destFile2 = Paths.get(tmpPath+"/dest/a.doc");
Path srcFile3 = Paths.get("/src/a.txt");
Path destFile3 = Paths.get(tmpPath+"/dest/a.txt");
Files.copy(srcFile1, destFile1);
Files.copy(srcFile2, destFile2);
Files.copy(srcFile3, destFile3);
delete(new File("/dest"));
tmp.renameTo("/dest");void delete(File f) throws IOException {
if (f.isDirectory()) {
for (File c : f.listFiles())
delete(c);
}
if (!f.delete())
throw new FileNotFoundException("Failed to delete file: " + f);
}只需覆盖文件:
String tmpPath="/tmp/in/same/partition/as/source";
File tmp=new File(tmpPath);
tmp.mkdirs();
Path srcFile1 = Paths.get("/src/a.pdf");
Path destFile1=paths.get("/dest/a.pdf");
Path tmp1 = Paths.get(tmpPath+"/a.pdf");
Path srcFile2 = Paths.get("/src/a.doc");
Path destFile2=Paths.get("/dest/a.doc");
Path tmp2 = Paths.get(tmpPath+"/a.doc");
Path srcFile3 = Paths.get("/src/a.txt");
Path destFile3=Paths.get("/dest/a.txt");
Path destFile3 = Paths.get(tmpPath+"/a.txt");
Files.copy(srcFile1, tmp1);
Files.copy(srcFile2, tmp2);
Files.copy(srcFile3, tmp3);
//Start of non atomic section(it can be done again if necessary)
Files.deleteIfExists(destFile1);
Files.deleteIfExists(destFile2);
Files.deleteIfExists(destFile2);
Files.move(tmp1,destFile1);
Files.move(tmp2,destFile2);
Files.move(tmp3,destFile3);
//end of non-atomic section即使第二种方法包含非原子节,复制过程本身也会使用临时目录,这样文件就不会被覆盖。
如果在移动文件的过程中进程中止,则可以轻松完成。
有关移动文件的参考信息,请参阅https://stackoverflow.com/a/4645271/10871900;有关递归删除目录的信息,请参阅https://stackoverflow.com/a/779529/10871900。
发布于 2020-09-28 01:08:13
首先,有几种复制文件或目录的可能性。Baeldung提供了对不同可能性的非常好的见解。此外,您还可以使用Spring中的FileCopyUtils。不幸的是,所有这些方法都不是原子的。
我已经找到了一个older post,并对其进行了一点调整。您可以尝试使用低级事务管理支持。这意味着您可以在该方法之外创建一个事务,并定义在回滚中应该执行的操作。还有一篇来自Baeldung的很好的文章。
@Autowired
private PlatformTransactionManager transactionManager;
@Transactional(rollbackOn = IOException.class)
public void copy(List<File> files) throws IOException {
TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition);
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCompletion(int status) {
if (status == STATUS_ROLLED_BACK) {
// try to delete created files
}
}
});
try {
// copy files
transactionManager.commit(transactionStatus);
} finally {
transactionManager.rollback(transactionStatus);
}
}或者,您可以使用一个简单的try-catch-block。如果抛出异常,您可以删除创建的文件。
发布于 2020-10-01 02:30:34
你的问题缺乏原子性的目标。即使解压缩也不是原子的,VM可能会崩溃,而OutOfMemoryError正好在膨胀第二个文件的块之间。所以有一个文件完整,第二个文件不完整,第三个文件完全丢失。
我能想到的唯一一件事就是两阶段提交,就像所有建议一样,临时目标突然变成了真正的目标。这样,您就可以确定,第二个操作要么永远不会发生,要么创建最终状态。
另一种方法是之后在目标中编写一种廉价的校验和文件。这将使外部进程很容易监听此类文件的创建,并使用找到的文件验证其内容。
后者与立即提供容器/ ZIP/ archive相同,而不是在目录中堆积文件。大多数归档文件都有或支持完整性检查。
(如果目录或文件夹在写入时消失,操作系统和文件系统的行为也会有所不同。有些人接受它并将所有数据写入可恢复缓冲区。其他人仍然接受写入,但不会更改任何内容。因为设备上的目标数据块未知,所以其他数据块在第一次写入时会立即失败。)
https://stackoverflow.com/questions/63835385
复制相似问题