如何在Java中从src原子复制多个文件到dest?

问题描述

在一项要求中,我需要将多个文件一个位置复制到另一个网络位置。

假设我在/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));
        }
    }
}

}