Java: Как искать дубликаты файлов в папке не только по имени, но также по размеру и содержимому? - PullRequest
1 голос
/ 02 июля 2019

Я хочу создать приложение Java для идентификации дубликатов. Пока я могу найти дубликаты только по имени, но мне также нужен размер, тип файла и, возможно, содержимое. Это мой код, использующий HashMap:

public static void find(Map<String, List<String>> lists, File dir) {
    for (File f : dir.listFiles()) {
        if (f.isDirectory()) {
            find(lists, f);
        } else {
            String hash = f.getName() + f.length();
            List<String> list = lists.get(hash);
            if (list == null) {
                list = new LinkedList<String>();
                lists.put(hash, list);
            }
            list.add(f.getAbsolutePath());
        }
    }
}

Ответы [ 4 ]

1 голос
/ 02 июля 2019

Учитывая, что два файла равны, если они имеют одинаковое расширение и одинаковый размер файла, это просто вопрос создания объекта, который представляет это «равенство».Итак, вы бы сделали что-то вроде:

public class FileEquality {
    private final String fileExtension;
    private final long fileSize;

    // constructor, toString, equals, hashCode, and getters here.
}

(и заполните все недостающие шаблоны: Constructor, toString, equals, hashCode и getters. См. Project Lombok's @ Value toсделай это легко, если хочешь).Вы можете получить расширение файла по имени файла, используя fileName.lastIndexOf('.') и fileName.substring(lastIndex).С lombok все, что вам нужно написать:

@lombok.Value public class FileEquality {
    String fileExtension;
    long fileSize;
}

Затем используйте FileEquality объекты в качестве ключей в вашей хэш-карте вместо строк.Однако то, что у вас есть, скажем, «foo.txt» и «bar.txt», размер которых составляет 500 байт, не означает, что эти 2 файла являются дубликатами.Итак, вы тоже хотите, чтобы контент включался, но, если вы расширите свой класс FileEquality, чтобы включить содержимое файла, то появятся 2 вещи:

  1. Если вы проверяете контентв любом случае, какое значение имеет размер и расширение файла?Если содержимое foo.txt и bar.jpg одинаково, они дубликаты, нет?Зачем беспокоиться.Вы можете передать содержимое как byte[], но обратите внимание, что написание правильной реализации hashCode() и equals() (которая требуется, если вы хотите использовать этот объект в качестве ключа для хэш-карт) становится немного сложнее.К счастью, @Value от lombok все поймет правильно, поэтому я предлагаю вам использовать это.

  2. Это означает, что полнота содержимого файла находится в памяти процесса вашей JVM,Если вы не проверяете очень маленькие файлы, вам просто не хватит памяти.Вы можете несколько абстрагироваться от этого, не сохраняя весь контент файла, а сохраняя хэш контента.Google вокруг, как вычислить sha-256 хеш файла в Java.Поместите это значение в ваш FileEquality, и теперь вы избежите проблемы с памятью.Теоретически возможно иметь 2 файла с разным содержимым, которые, тем не менее, хэшируют с одинаковым значением sha-256, но вероятность этого астрономическая, и, более того, sha-256 разработан таким образом, что его невозможно математически преднамеренносделайте 2 таких файла, чтобы связываться с вашим приложением.Поэтому я предлагаю вам просто доверять хешу:)

Обратите внимание, конечно, что хеширование всего файла требует чтения всего файла, поэтому, если вы запускаете поиск дубликатов в каталоге, содержащемскажем, файлы объемом 500 ГБ, тогда вашему приложению потребуется минимальное чтение 500 ГБ, что займет некоторое время.

0 голосов
/ 02 июля 2019

копипаст-пример

  1. создать класс, который расширяет File

    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.util.Arrays;
    
    public class MyFile extends File {
        private static final long serialVersionUID = 1L;
    
        public MyFile(final String pathname) {
            super(pathname);
        }
    
        @Override
        public boolean equals(final Object obj) {
            if (this == obj) {
                return true;
            }
            if (this.getClass() != obj.getClass()) {
                return false;
            }
            final MyFile other = (MyFile) obj;
            if (!Arrays.equals(this.getContent(), other.getContent())) {
                return false;
            }
            if (this.getName() == null) {
                if (other.getName() != null) {
                    return false;
                }
            } else if (!this.getName().equals(other.getName())) {
                return false;
            }
            if (this.length() != other.length()) {
                return false;
            }
            return true;
        }
    
        @Override
        public int hashCode() {
            final int prime = 31;
            int result = prime;
            result = (prime * result) + Arrays.hashCode(this.getContent());
            result = (prime * result) + ((this.getName() == null) ? 0 : this.getName().hashCode());
            result = (prime * result) + (int) (this.length() ^ (this.length() >>> 32));
            return result;
        }
    
        private byte[] getContent() {
            try (final FileInputStream fis = new FileInputStream(this)) {
                return fis.readAllBytes();
            } catch (final IOException e) {
                e.printStackTrace();
                return new byte[] {};
            }
        }
    }
    
  2. читать базовый каталог

    import java.io.File;
    import java.util.HashMap;
    import java.util.Iterator;
    import java.util.List;
    import java.util.Map;
    import java.util.Map.Entry;
    import java.util.Vector;
    
    public class FileTest {
        public FileTest() {
            super();
        }
    
        public static void main(final String[] args) {
            final Map<MyFile, List<MyFile>> duplicates = new HashMap<>();
            FileTest.handleDirectory(duplicates, new File("[path to base directory]"));
            final Iterator<Entry<MyFile, List<MyFile>>> iterator = duplicates.entrySet().iterator();
            while (iterator.hasNext()) {
                final Entry<MyFile, List<MyFile>> next = iterator.next();
                if (next.getValue().size() == 0) {
                    iterator.remove();
                } else {
                    System.out.println(next.getKey().getName() + " - " + next.getKey().getAbsolutePath());
                    for (final MyFile file : next.getValue()) {
                        System.out.println("        ->" + file.getName() + " - " + file.getAbsolutePath());
                    }
                }
            }
        }
    
        private static void handleDirectory(final Map<MyFile, List<MyFile>> duplicates, final File directory) {
            final File dir = directory;
            if (dir.isDirectory()) {
                final File[] files = dir.listFiles();
                for (final File file : files) {
                    if (file.isDirectory()) {
                        FileTest.handleDirectory(duplicates, file);
                        continue;
                    }
                    final MyFile myFile = new MyFile(file.getAbsolutePath());
                    if (!duplicates.containsKey(myFile)) {
                        duplicates.put(myFile, new Vector<>());
                    } else {
                        duplicates.get(myFile).add(myFile);
                    }
                }
            }
        }
    }
    
0 голосов
/ 02 июля 2019

Я использовал MessageDigest и проверил некоторые файлы и нашел дубликаты в соответствии со всеми критериями, которые я перечислил в заголовке и описании.Спасибо всем.

private static MessageDigest messageDigest;
static {
    try {
        messageDigest = MessageDigest.getInstance("SHA-512");
    } catch (NoSuchAlgorithmException e) {
        throw new RuntimeException("cannot initialize SHA-512 hash function", e);
    }
}   

и это результат после внедрения в код поиска для дубликатов

public static void find(Map<String, List<String>> lists, File dir) {
for (File f : dir.listFiles()) {
  if (f.isDirectory()) {
    find(lists, f);
  } else {
      try{
          FileInputStream fi = new FileInputStream(f);
          byte fileData[] = new byte[(int) f.length()];
                fi.read(fileData);
                fi.close();
                //Crearea id unic hash pentru fisierul curent
                String hash = new BigInteger(1, messageDigest.digest(fileData)).toString(16);
                List<String> list = lists.get(hash);
                if (list == null) {
                    list = new LinkedList<String>();
                }
                //Adăugați calea către listă
                list.add(f.getAbsolutePath());
                //Adauga lista actualizată la tabelul Hash
                lists.put(hash, list);

      }catch (IOException e) {
                throw new RuntimeException("cannot read file " + f.getAbsolutePath(), e);
            }

  }
}

}

0 голосов
/ 02 июля 2019

Я сделал это приложение давно. Я нашел для вас часть его исходного кода, если вы хотите изучить.

Этот метод работает путем сравнения обоих файлов в байтах.

public static boolean checkBinaryEquality(File file1, File file2) {
    if(file1.length() != file2.length()) return false;
    try(FileInputStream f1 = new FileInputStream(file1); FileInputStream f2 = new FileInputStream(file2)){
            byte bus1[] = new byte[1024],
                 bus2[] = new byte[1024];
            // comparing files bytes one by one if we found unmatched results that means they are not equal
            while((f1.read(bus1)) >= 0) {
                f2.read(bus2);
                for(int i = 0; i < 1024;i++)
                    if(bus1[i] != bus2[i]) 
                        return false;
            }
            // passed
            return true;
    } catch (IOException exp) {
        // problems occurred so let's consider them not equal
        return false;
    }
}

объединениеэтот метод с проверкой имени и расширения, и вы готовы к работе.

...