Skip to end of metadata
Go to start of metadata

Ritkán előforduló feladat egy fájl vagy egyéb stream tartalmából MD5 ellenőrző összeget készíteni, ám viszonylag hamar meg lehet találni a jó megoldást (a try-catch blokkot mindenki képzelje a forráskód köré):

MessageDigest checksum;
checksum = MessageDigest.getInstance("MD5");
byte[] buf = new byte[1024];
FileInputStream fis = new FileInputStream(file);
while (true)
{
  int len = fis.read(buf, 0, buf.length);
  if (len <= 0)
  {
  break;
  }
  checksum.update(buf, 0, len);
}
byte[] checksumValue = checksum.digest();

Az eredményképp előálló checksumValue egyik apró problémája, hogy byte tömb, amelyből általában hexadecimális eredményt szeretnénk kapni. Több megoldás közül is választhatunk, az egyik legrövidebb:

BigInteger checksumBigValue = new BigInteger(1, checksumValue);
String.format("%032x", checksumBigValue);

Ebben az esetben a BigInteger azon konstruktorát használjuk, amely az eredményül kapott tömbből egy BigInteger példányt készít, majd ebből a String.format metódusával előállítjuk a 32 karakter hosszú hexadecimális számot a bevezető nullákkal együtt. Ez egy szép, egyszerű, kényelmes, ellenben lassú megoldás, 10 millió futásra (három mérésből) 21,6 és 22,7 másodperc közötti értékek jöttek elő (ha egy sorba szervezzük, akkor se lesz gyorsabb, 21,8 és 22,6 másodperc közötti eredmények születtek). Külön mérve a két sort kiderül, hogy a BigInteger viszonylag jó hatékonysággal készít példányt magából, mivel 0,38 és 0,42 másodperc közötti futásidőket sikerült mérni, megállapítható, hogy a String.format nem túl gyors, mivelhogy általános célra szolgál.

A kérdés, hogy vannak-e gyorsabb megoldások, amelyek BigInteger példányból hexadecimális kimenetet készítenek. Az első tipp a BigInteger.toString metódusa, amely képes különféle számrendszerekre konvertálni, így hexadecimális számrendszerbe is:

String md5Sum = checksumBigValue.toString(16);

A megoldás viszonylag gyors, 10 millió futásra 12,3-13,1ms közötti értéket kapunk, amely bőven alatta van a byte tömbből való BigInteger készítésnek, amely 380-320ms ideig fut azonos körülmények között.

Ha nem figyelünk rendesen, akkor dolgunk végeztével nyugodtan hátradőlünk, miközben a programunk futása hibás, mert toString metódus nem törődik a bevezető nullákkal, így szélsőséges esetben az alábbi eredményt kapjuk:

0

Amely helyett 32 darab nullát kellene kapnunk:

00000000000000000000000000000000

A különbség komoly problémákat okozhat, ha két MD5 hexadecimális érték különbözőségére alapozunk egy megoldást.

Van tehát egy gyors, ám hibás megoldásunk, amelyet kicsit csiszolnunk kell: ki kell egészíteni 32 karakterre a kapott hexadecimális eredményt.

Az egyik közkedvelt megoldás 1,70-1,72 másodperc közötti időket fut:

StringBuilder sb = new StringBuilder(64);
sb.append("00000000000000000000000000000000");
sb.append(md5Sum);
result = sb.substring(md5Sum.length());

Ezt egy kis trükkel (thx pgm) 1,56-1,61 másodpercre tudjuk leszorítani:

StringBuilder sb = new StringBuilder("00000000000000000000000000000000");
sb.setLength(32-md5Sum.length());
sb.append(md5Sum);

Ez utóbbi megoldással a teljes MD5 ellenőrző összeg létrehozását leszorítottuk a 21-22 másodpercről 1,86-1,92 másodpercre, amely több mint tízszeres javulás.

 

Van gyorsabb megoldás? (smile)

      
      
Page viewed times

3 Comments

  1. Anonymous

    Itt is van par hasznalhato otlet: http://stackoverflow.com/questions/304268/getting-a-files-md5-checksum-in-java

    Itt meg a bytearray-to-hex-string gyors megvalositasa (nem ellenoriztem hogy tenyleg gyorsabb-e): http://www.rgagnon.com/javadetails/java-0596.html

  2. A StackOverflow-s megoldások kicsit lassabbak ezeknél.

    ByteArray-to-Hex mindenképp gyorsabb, de annyival rondább a kódja, hogy azt inkább nem forszíroztuk.

  3. Anonymous

    Érdekes, én eddig így csináltam ->

    byte s[] = m.digest();
    String result = "";
    for (int i = 0; i < s.length; i++) {
     result += Integer.toHexString((0x000000ff & s[i]) | 0xffffff00).substring(6);
    }

    loolek

#trackbackRdf ($trackbackUtils.getContentIdentifier($page) $page.title $trackbackUtils.getPingUrl($page))