Eu fiz um método que verifica se dois arquivos (texto ou binários não importa) são identicos ou não. Entretanto a execução do método está demorando demais quando os arquivos idênticos são um pouco maiores (no meu exemplo eu tenho várias duplicidades acima de 20 Mb) .
Um simples comando diff (unix) está resolvendo o mesmo problema muito mais rápido. Gostaria de saber se existe alguma maneira de se reescrever esse mesmo método de forma a fazê-lo ficar mais eficiente.
Alguém aí poderia me ajudar com isso?
segue o método:
public static boolean verificaDuplicidade(String file1, String file2) {
File f1 = new File(file1);
File f2 = new File(file2);
int byte_f1;
int byte_f2;
if (f1.length() == f2.length()) {
try {
InputStream isf1 = new FileInputStream(f1);
InputStream isf2 = new FileInputStream(f2);
for (long i = 0; i <= f1.length(); i++) {
try {
byte_f1 = isf1.read();
byte_f2 = isf2.read();
if (byte_f1 != byte_f2) {
isf1.close();
isf2.close();
return false; // tamanhos iguais e conteudos diferentes
}
} catch (IOException ex) {
}
}
} catch (FileNotFoundException ex) {
}
} else {
return false; // tamanho e conteudo diferente
}
return true; // arquivos iguais
}
Por que você não cria Threads para comparar ranges de bytes? Tipo, para cada 10MB você cria uma Thread, sendo que possa ter no maximo 10 Threads comparando os ranges de um In/OutputStream.
Edit:
Use tambem BufferedOutput/InputStream que ja deve ajudar a lot
Seu problema não é de threads e sim de comparar os arquivos byte a byte. Isso torna a leitura de arquivos excessivamente lenta. Leia os arquivos de 1MB em 1MB (por exemplo), e compare os arrays de 1MB entre si.
Rode este programa em seu disco, e surpreenda-se com a quantidade de arquivos duplicados entre si.
Ele acha recursivamente os arquivos que são iguais entre si.
import java.util.*;
import java.io.*;
import java.security.*;
import java.math.*;
class AcharArquivosIguais {
public AcharArquivosIguais() {
}
public void listarArquivos (File diretorio, Set<File> arquivos) {
File[] listagem = diretorio.listFiles();
for (File f : listagem) {
if (f.isDirectory() && !f.getName().equals (".") && !f.getName().equals ("..")) {
listarArquivos (f, arquivos);
} else {
arquivos.add (f);
}
}
}
private static String hashFile (File arq) {
String s = "error";
try {
MessageDigest dgst = MessageDigest.getInstance ("SHA1");
FileInputStream fis = new FileInputStream (arq);
byte[] buffer = new byte[20480];
int nBytes;
dgst.reset();
while ((nBytes = fis.read (buffer)) > 0) {
dgst.update (buffer, 0, nBytes);
}
byte[] bytes = dgst.digest();
fis.close();
BigInteger bd = new BigInteger (bytes);
s = bd.toString (16);
} catch (NoSuchAlgorithmException ex) {
} catch (IOException ex) {
}
return s;
}
public void acharArquivosIguais (Collection<File> arquivos) {
Map<String, List><File>> hash2file = new TreeMap<String, List><File>>();
for (File f : arquivos) {
System.out.print ("\r" + f);
String hash = hashFile (f);
List<File> files;
if (!hash2file.containsKey (hash)) {
files = new ArrayList<File>();
hash2file.put (hash, files);
} else {
files = hash2file.get (hash);
}
files.add (f);
}
System.out.println ();
// Agora vamos ver, nessa lista, que arquivos têm mais de uma entrada.
boolean arquivosRepetidos = false;
for (Map.Entry<String, List><File>> h2f : hash2file.entrySet()) {
if (h2f.getValue().size() > 1) {
arquivosRepetidos = true;
System.out.println ("---");
System.out.println (h2f.getValue().size() + " arquivos com o hash " + h2f.getKey() + ":");
System.out.println ("---");
for (File f : h2f.getValue()) {
System.out.println (" " + f);
}
}
}
if (!arquivosRepetidos) {
System.out.println ("Não foram encontrados arquivos repetidos.");
}
}
public static void main(String[] args) {
if (args.length != 1) {
System.err.println ("Sintaxe: java -cp . AcharArquivosIguais diretorio");
System.exit (1);
}
AcharArquivosIguais aai = new AcharArquivosIguais();
Set<File> arquivos = new TreeSet<File>();
aai.listarArquivos (new File (args[0]), arquivos);
aai.acharArquivosIguais (arquivos);
}
}
O seu programa ainda está errado, se algum dos arquivos não for múltiplo de 1MB. Você precisa ver quantos bytes foram lidos (é o retorno de read), e comparar apenas a quantidade de bytes lidos por read, não os arrays completos.
Observando mais cuidadosamente o programa, reparei que o método read() não zera o array. Entretanto na última leitura do arquivo quando o mesmo não é multiplo de 1Mb (ou do tamanho do meu buffer) o método read() preenche apenas as posições retornadas, mantendo as demais posiçõesdo array com os mesmos valores obtidos na leitura anterior.
Sendo assim, o programa não estava própriamente errado, mas estava fazendo desnecessáriamente a leitura desses bytes adicionais, causando uma queda no desempenho da execução.
Todavia, a sua observação serviu para que eu pudesse otimizar ainda mais o meu programa, pois realmente não havia percebido essas leituras desnecessárias.
segue a nova versão do método:
public static boolean verificaDuplicidade(String file1, String file2) {
File f1 = new File(file1);
File f2 = new File(file2);
byte[] f1_buf = new byte[1048576];
byte[] f2_buf = new byte[1048576];
int len;
if (f1.length() == f2.length()) {
try {
InputStream isf1 = new FileInputStream(f1);
InputStream isf2 = new FileInputStream(f2);
try {
while (isf1.read(f1_buf) >= 0) {
len=isf2.read(f2_buf);
for (int j = 0; j < len; j++) {
if (f1_buf[j] != f2_buf[j]) {
return false; // tamanho igual e conteudo diferente
}
}
}
} catch (IOException e) {
}
} catch (FileNotFoundException e) {
}
} else {
return false; // tamanho e conteudo diferente
}
return true; // arquivos iguais
}