问题描述
在一项要求中,我需要将多个文件从一个位置复制到另一个网络位置。
假设我在/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
并在目标位置解压缩。
这种方法是否正确,才能使整个复制过程成为原子操作?任何人都会遇到类似的概念并予以解决。
解决方法
这种方法是否正确,才能使整个复制过程成为原子操作?任何人都会遇到类似的概念并予以解决。
您可以将文件复制到新的临时目录,然后重命名目录。
重命名临时目录之前,需要删除目标目录
如果您不想覆盖目标目录中的其他文件,则可以将所有文件从临时目录移动到目标目录。
但这不是完全原子的。
删除/ 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。
,首先,有几种可能性可以复制文件或目录。 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。如果引发异常,则可以删除创建的文件。
,您的问题缺乏原子性的目标。即使解压缩从来都不是原子的,VM可能在填充第二个文件的块之间,由于OutOfMemoryError而崩溃。因此,其中一个文件完整,第二个没有,而第三个完全丢失。
我唯一想到的就是两阶段提交,就像所有带有临时目标的建议突然变成真正的目标一样。这样,您可以确保第二个操作永远不会发生或创建最终状态。
另一种方法是随后在目标中写入一种廉价的校验和文件。这样,外部进程就可以轻松侦听此类文件的创建,并使用找到的文件来验证其内容。
后者类似于立即提供容器/ ZIP /档案,而不是将文件堆积在目录中。大多数档案都具有或支持完整性检查。
(如果目录或文件夹在写入时消失,则操作系统和文件系统的行为也会有所不同。一些系统接受文件并将所有数据写入可恢复的缓冲区。其他系统仍然接受写入但不更改任何内容。其他系统在首次启动后立即失败由于设备上的目标块未知,因此无法写入。)
,原子写入:
标准文件系统没有原子性概念,因此您只需要执行单个操作-那将是原子性的。
因此,要以原子方式写入更多文件,您需要创建一个文件夹,例如,其名称带有时间戳,然后将文件复制到该文件夹中。
然后,您可以将其重命名为最终目标,也可以创建符号链接。
您可以使用与此类似的任何东西,例如Linux上基于文件的卷等。
请记住,删除现有的符号链接并创建一个新的符号链接绝不会是原子的,因此您需要处理代码中的情况,并在可用时切换到重命名/链接的文件夹,而不是删除/创建链接。但是,在正常情况下,删除和创建新链接是非常快的操作。
原子读取:
好吧,问题不在于代码,而在于操作系统/文件系统级别。
前一段时间,我遇到了非常相似的情况。有一个数据库引擎正在运行,并且“一次”更改多个文件。我需要复制当前状态,但是在复制第一个文件之前,已经更改了第二个文件。
有两种不同的选择:
使用支持快照的文件系统。有时,您创建快照,然后从中复制文件。
您可以使用fsfreeze --freeze
锁定文件系统(在Linux上),稍后使用fsfreeze --unfreeze
对其进行解锁。冻结文件系统后,您可以照常读取文件,但是没有进程可以更改它们。
这些选项都不适合我,因为我无法更改文件系统类型,并且无法锁定文件系统(它是根文件系统)。
我创建了一个空文件,将其安装为loop
文件系统,并对其进行了格式化。从那时起,我可以fsfreeze
仅使用虚拟卷,而无需接触根文件系统。
我的脚本首先调用fsfreeze --freeze /my/volume
,然后执行复制操作,然后调用fsfreeze --unfreeze /my/volume
。在复制操作期间,无法更改文件,因此复制的文件都是完全在同一时间创建的-就我而言,这就像是原子操作。
顺便说一句,请确保不要fsfreeze
您的根文件系统:-)。我做到了,重启是唯一的解决方案。
类似数据库的方法:
即使数据库也不能依赖原子操作,因此它们首先将更改写入WAL(预写日志)并将其刷新到存储中。刷新后,他们可以将更改应用于数据文件。
如果有任何问题/崩溃,数据库引擎将首先加载数据文件并检查WAL中是否有一些未应用的事务,然后最终应用它们。
这也称为日记记录,某些文件系统(ext3,ext4)使用它。
,我希望该解决方案会有用:根据我的理解,您需要将文件从一个目录复制到另一目录。 所以我的解决方案如下: 谢谢!!
公共类CopyFilesDirectoryProgram {
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
String sourcedirectoryName="//mention your source path";
String targetdirectoryName="//mention your destination path";
File sdir=new File(sourcedirectoryName);
File tdir=new File(targetdirectoryName);
//call the method for execution
abc (sdir,tdir);
}
private static void abc(File sdir,File tdir) throws IOException {
if(sdir.isDirectory()) {
copyFilesfromDirectory(sdir,tdir);
}
else
{
Files.copy(sdir.toPath(),tdir.toPath());
}
}
private static void copyFilesfromDirectory(File source,File target) throws IOException {
if(!target.exists()) {
target.mkdir();
}else {
for(String items:source.list()) {
abc(new File(source,items),new File(target,items));
}
}
}
}