带有大文件的Java CRC(Adler)

带有大文件的Java CRC(Adler),java,checksum,large-files,Java,Checksum,Large Files,我有以下情况:一个包含大文件的目录树(大约5000个~4Gb大小的文件)。我需要在这棵树上找到重复的 我尝试使用Java内置的CRC32和Adler32类,但速度非常慢(每个文件大约3-4分钟) 代码是这样的: 1) Init map <path, checksum> 2) Create CheckSum instance (CRC32 or Adler32); 3) Read file per block (10-100 bytes); 4) In each iteration c

我有以下情况:一个包含大文件的目录树(大约5000个~4Gb大小的文件)。我需要在这棵树上找到重复的

我尝试使用Java内置的CRC32和Adler32类,但速度非常慢(每个文件大约3-4分钟)

代码是这样的:

1) Init map <path, checksum>
2) Create CheckSum instance (CRC32 or Adler32);
3) Read file per block (10-100 bytes);
4) In each iteration call update()
5) Result checksum passed in map <path, summ>
6) Find duplicates
1)初始化映射
2) 创建校验和实例(CRC32或Adler32);
3) 每个块读取文件(10-100字节);
4) 在每次迭代调用update()中
5) 映射中传递的结果校验和
6) 查找重复项

问题:有没有办法加快第3-4行中校验和的收集速度?

我将以以下方式处理此问题:

  • 试着使用Java8并行流或类似的东西,这样我的多核CPU就可以用来加快速度
  • 用所有文件填充地图
  • 获取所有文件大小
  • 消除所有具有唯一文件大小的文件。如果您的文件“非常正常”,那么很可能已经删除了几个较大的文件。这实际上可以通过使用文件大小作为映射中的键和列表作为值来实现。然后删除列表大小为1的所有条目
  • 获取每个文件的下一个(首次:首次)4K字节的校验和
  • 消除所有具有唯一校验和的文件
  • 重复4次。五,。直到没有唯一校验和的文件都被完全读取
  • 其余文件是重复的文件(如果校验和没有冲突)
  • 比较重复文件的文件内容以防冲突
  • 加速校验和的关键是将校验和分块进行,并在两者之间进行比较。如果第一个字节已经不同,为什么还要看其余的字节呢

    加速比较的另一个关键可能是反转地图。我会使用
    Map
    而不是
    Map
    。 通过这种方式,您可以直接删除所有列表大小为1的条目,而无需进一步查找或比较

    也许还有比这更聪明的方法,当我读到这个任务的时候,我就把脑子里想的都抛在脑后

    我已经草拟了一个小程序,几乎可以执行这一点。它不获取片段中的校验和。这是因为当我在236670个文件上运行这个程序时,总共有6GB的数据,只花了7秒钟。免责声明:我有一个SSD。但也许我会更新部分校验和的程序

    import java.io.*;
    import java.nio.file.*;
    import java.nio.file.attribute.*;
    import java.util.*;
    import java.util.concurrent.*;
    import java.util.function.*;
    import java.util.stream.*;
    import java.util.zip.*;
    
    public class FindDuplicates {
        public static void main(final String... args) throws IOException {
            findDuplicates(argsOrCurrentDirectory(args));
        }
    
        private static String[] argsOrCurrentDirectory(final String... args) {
            return args.length == 0 ? new String[] {"."} : args;
        }
    
        private static void findDuplicates(final String... paths) throws IOException {
            final Stream<Path> allFilesInPaths = find(paths);
    
            final Map<Long, List<Path>> filesBySize = allFilesInPaths.collect(Collectors.groupingByConcurrent(path -> path.toFile().length()));
            final Stream<Path> filesWithNonUniqueSizes = getValueStreamFromDuplicates(filesBySize);
    
            final Map<Long, List<Path>> filesByChecksum = filesWithNonUniqueSizes.collect(Collectors.groupingBy(FindDuplicates::getChecksum));
            final Stream<Path> filesWithNonUniqueChecksums = getValueStreamFromDuplicates(filesByChecksum);
    
            filesWithNonUniqueChecksums.forEach(System.out::println);
        }
    
        private static Stream<Path> toPaths(final String... pathnames) {
            return Arrays.asList(pathnames).parallelStream().map(FileSystems.getDefault()::getPath);
        }
    
        private static Stream<Path> find(final String... pathnames) {
            return find(toPaths(pathnames));
        }
    
        private static Stream<Path> find(final Stream<Path> paths) {
            return paths.flatMap(FindDuplicates::findSinglePath);
        }
    
        private static Stream<Path> findSinglePath(final Path path) {
            try {
                return Files.find(path, 127, ($, attrs) -> attrs.isRegularFile());
            } catch (final IOException e) {
                System.err.format("%s: error: Unable to traverse path: %s%n", path, e.getMessage());
                return Stream.empty();
            }
        }
    
        public static <V> Stream<V> getValueStreamFromDuplicates(final Map<?, List<V>> original) {
            return original.values().parallelStream().filter(list -> list.size() > 1).flatMap(Collection::parallelStream);
        }
    
        public static long getChecksum(final Path path) {
            try (final CheckedInputStream in = new CheckedInputStream(new BufferedInputStream(new FileInputStream(path.toFile())), new CRC32())) {
                return tryGetChecksum(in);
            } catch (final IOException e) {
                System.err.format("%s: error: Unable to calculate checksum: %s%n", path, e.getMessage());
                return 0L;
            }
        }
    
        public static long tryGetChecksum(final CheckedInputStream in) throws IOException {
            final byte[] buf = new byte[4096];
            for (int bytesRead; (bytesRead = in.read(buf)) != -1; );
            return in.getChecksum().getValue();
        }
    }
    
    import java.io.*;
    导入java.nio.file.*;
    导入java.nio.file.attribute.*;
    导入java.util.*;
    导入java.util.concurrent.*;
    导入java.util.function.*;
    导入java.util.stream.*;
    导入java.util.zip.*;
    公共类查找副本{
    公共静态void main(最终字符串…args)引发IOException{
    findDuplicates(argsOrCurrentDirectory(args));
    }
    私有静态字符串[]argsOrCurrentDirectory(最终字符串…args){
    返回args.length==0?新字符串[]{“.”}:args;
    }
    私有静态void findDuplicates(最终字符串…路径)引发IOException{
    最终流allFilesInPaths=find(路径);
    final-Map-filesBySize=allFilesInPaths.collect(Collectors.groupingByConcurrent(path->path.toFile().length());
    最终流fileswithununiquesizes=getValueStreamFromDuplicates(filesBySize);
    final-Map filesByChecksum=fileswithununiquesizes.collect(Collectors.groupingBy(FindDuplicates::getChecksum));
    最终流fileswithununiquechecksums=getValueStreamFromDuplicates(filesByChecksum);
    fileswithununiquechecksums.forEach(System.out::println);
    }
    私有静态流toPaths(最终字符串…路径名){
    返回Arrays.asList(路径名).parallelStream().map(FileSystems.getDefault()::getPath);
    }
    私有静态流查找(最终字符串…路径名){
    返回find(toPaths(路径名));
    }
    专用静态流查找(最终流路径){
    返回路径.flatMap(FindDuplicates::findSinglePath);
    }
    私有静态流findSinglePath(最终路径){
    试一试{
    返回Files.find(路径127,($,attrs)->attrs.isRegularFile());
    }捕获(最终IOE例外){
    System.err.format(“%s:error:无法遍历路径:%s%n”,路径,例如getMessage());
    返回Stream.empty();
    }
    }
    
    公共静态流getValueStreamFromDuplicates(final Map我将以以下方式处理此问题:

  • 试着使用Java8并行流或类似的东西,这样我的多核CPU就可以用来加快速度
  • 用所有文件填充地图
  • 获取所有文件大小
  • 删除所有具有唯一文件大小的文件。如果您的文件“非常正常”,则很可能已经删除了多个较大的文件。实际上,可以通过将文件大小用作映射中的键,将列表用作值来完成此操作。然后删除列表大小为1的所有条目
  • 获取每个文件的下一个(首次:首次)4K字节的校验和
  • 消除所有具有唯一校验和的文件
  • 重复4.和5.直到没有唯一校验和的文件都被完全读取
  • 其余文件是重复的文件(如果校验和没有冲突)
  • 比较重复文件的文件内容以防冲突
  • 加速校验和的关键是分块进行校验和比较。如果第一个字节已经不同,为什么还要看其余的字节呢

    加速比较的另一个关键可能是反转地图。我会使用
    Map
    而不是
    Map
    。 通过这种方式,您可以直接删除所有列表大小为1的条目,而无需进一步查找或比较

    也许还有比这更聪明的方法,当我读到这个任务的时候,我就把脑子里想的都抛在脑后

    我已经草拟了一个小程序,几乎可以执行这项工作。它不会在片段中获取校验和。原因是