FileChannel ByteBuffer und Hashing-Dateien
Ich habe eine Datei-Hashing-Methode in Java erstellt, die die Eingabezeichenfolgendarstellung von a übernimmtfilepath+filename
und berechnet dann den Hash dieser Datei. Der Hash kann ein beliebiger Java-Hashing-Algorithmus sein, wie zMD2
durchSHA-512
.
Ich versuche, jeden Tropfen der Leistung herauszufinden, da diese Methode ein wesentlicher Bestandteil eines Projekts ist, an dem ich arbeite. Mir wurde geraten, es mit zu versuchenFileChannel
anstelle eines regulärenFileInputStream
.
Meine ursprüngliche Methode:
/**
* Gets Hash of file.
*
* @param file String path + filename of file to get hash.
* @param hashAlgo Hash algorithm to use. <br/>
* Supported algorithms are: <br/>
* MD2, MD5 <br/>
* SHA-1 <br/>
* SHA-256, SHA-384, SHA-512
* @return String value of hash. (Variable length dependent on hash algorithm used)
* @throws IOException If file is invalid.
* @throws HashTypeException If no supported or valid hash algorithm was found.
*/
public String getHash(String file, String hashAlgo) throws IOException, HashTypeException {
StringBuffer hexString = null;
try {
MessageDigest md = MessageDigest.getInstance(validateHashType(hashAlgo));
FileInputStream fis = new FileInputStream(file);
byte[] dataBytes = new byte[1024];
int nread = 0;
while ((nread = fis.read(dataBytes)) != -1) {
md.update(dataBytes, 0, nread);
}
fis.close();
byte[] mdbytes = md.digest();
hexString = new StringBuffer();
for (int i = 0; i < mdbytes.length; i++) {
hexString.append(Integer.toHexString((0xFF & mdbytes[i])));
}
return hexString.toString();
} catch (NoSuchAlgorithmException | HashTypeException e) {
throw new HashTypeException("Unsuppored Hash Algorithm.", e);
}
}
Refactored Methode:
/**
* Gets Hash of file.
*
* @param file String path + filename of file to get hash.
* @param hashAlgo Hash algorithm to use. <br/>
* Supported algorithms are: <br/>
* MD2, MD5 <br/>
* SHA-1 <br/>
* SHA-256, SHA-384, SHA-512
* @return String value of hash. (Variable length dependent on hash algorithm used)
* @throws IOException If file is invalid.
* @throws HashTypeException If no supported or valid hash algorithm was found.
*/
public String getHash(String fileStr, String hashAlgo) throws IOException, HasherException {
File file = new File(fileStr);
MessageDigest md = null;
FileInputStream fis = null;
FileChannel fc = null;
ByteBuffer bbf = null;
StringBuilder hexString = null;
try {
md = MessageDigest.getInstance(hashAlgo);
fis = new FileInputStream(file);
fc = fis.getChannel();
bbf = ByteBuffer.allocate(1024); // allocation in bytes
int bytes;
while ((bytes = fc.read(bbf)) != -1) {
md.update(bbf.array(), 0, bytes);
}
fc.close();
fis.close();
byte[] mdbytes = md.digest();
hexString = new StringBuilder();
for (int i = 0; i < mdbytes.length; i++) {
hexString.append(Integer.toHexString((0xFF & mdbytes[i])));
}
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
throw new HasherException("Unsupported Hash Algorithm.", e);
}
}
Beide geben einen korrekten Hash zurück, die überarbeitete Methode scheint jedoch nur bei kleinen Dateien zusammenzuarbeiten. Wenn ich eine große Datei übergebe, wird sie vollständig erstickt und ich kann nicht herausfinden, warum. Ich bin neu inNIO
also bitte beraten.
EDIT: Ich habe vergessen zu erwähnen, dass ich SHA-512's zum Testen durchwerfe.
UPDATE:
Aktualisierung mit meiner jetzt aktuellen Methode.
/**
* Gets Hash of file.
*
* @param file String path + filename of file to get hash.
* @param hashAlgo Hash algorithm to use. <br/>
* Supported algorithms are: <br/>
* MD2, MD5 <br/>
* SHA-1 <br/>
* SHA-256, SHA-384, SHA-512
* @return String value of hash. (Variable length dependent on hash algorithm used)
* @throws IOException If file is invalid.
* @throws HashTypeException If no supported or valid hash algorithm was found.
*/
public String getHash(String fileStr, String hashAlgo) throws IOException, HasherException {
File file = new File(fileStr);
MessageDigest md = null;
FileInputStream fis = null;
FileChannel fc = null;
ByteBuffer bbf = null;
StringBuilder hexString = null;
try {
md = MessageDigest.getInstance(hashAlgo);
fis = new FileInputStream(file);
fc = fis.getChannel();
bbf = ByteBuffer.allocateDirect(8192); // allocation in bytes - 1024, 2048, 4096, 8192
int b;
b = fc.read(bbf);
while ((b != -1) && (b != 0)) {
bbf.flip();
byte[] bytes = new byte[b];
bbf.get(bytes);
md.update(bytes, 0, b);
bbf.clear();
b = fc.read(bbf);
}
fis.close();
byte[] mdbytes = md.digest();
hexString = new StringBuilder();
for (int i = 0; i < mdbytes.length; i++) {
hexString.append(Integer.toHexString((0xFF & mdbytes[i])));
}
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
throw new HasherException("Unsupported Hash Algorithm.", e);
}
}
Daher habe ich versucht, das MD5 einer 2,92-GB-Datei anhand meines ursprünglichen Beispiels und des Beispiels meines neuesten Updates einem Benchmark zu unterziehen. Natürlich ist jeder Benchmark relativ, da OS- und Disk-Caching und andere "magische" Dinge vor sich gehen, die das wiederholte Lesen der gleichen Dateien verzerren ... aber hier ist ein Überblick über einige Benchmarks. Ich lud jede Methode auf und feuerte sie fünfmal ab, nachdem ich sie neu kompiliert hatte. Der Benchmark wurde aus dem letzten (5.) Durchlauf genommen, da dies der "heißeste" Durchlauf für diesen Algorithmus und jede "Magie" wäre (in meiner Theorie jedenfalls).
Here's the benchmarks so far:
Original Method - 14.987909 (s)
Latest Method - 11.236802 (s)
Das ist ein25.03% decrease
in der Zeit genommen, um die gleiche 2.92GB-Datei zu hashen. Ziemlich gut.