1. 程式人生 > >MD5檔案加密以及關於NIO中的FileChannel.map的一點看法

MD5檔案加密以及關於NIO中的FileChannel.map的一點看法

前些天忽然對MD5的加密很感興趣。而也發現JAVA的API中java.security.MessageDigest 並沒有提供直接用於檔案的方法。而我其實挺需要這個方法的,所以決定自己寫一個。
最初的版本是把檔案全讀入記憶體為byte[],然後用API加密:
import org.apache.commons.io.IOUtils;
import org.apache.commons.codec.digest.DigestUtils;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class MD5Util{
public static void main(String[] args){
          if(args.length != 1){
            System.out.println("Incorrect parameter counts.");
            System.exit(-1);
          }
          FileInputStream fin = null;
          try{
            fin = new FileInputStream(args[0]);
            System.out.println(DigestUtils.md5Hex(IOUtils.toByteArray(fin)));
    }catch(FileNotFoundException e){
      System.out.println("File not found: "+args[0]);
      System.exit(-1);
        }catch(IOException e){
      System.out.println("File IO error: "+e);
      System.exit(-1);
        }

}
}

這樣對小檔案的處理還是很快的,不過檔案一大起來,它就會造成記憶體溢位。
初時不知怎麼處理,上網查了一下,發現有兩個解決辦法的:
一,用NIO的ByteBuffer,先用FileChannel.map取得一個ByteBuffer,把檔案對映入記憶體,然後再幹。
         FileChannel ch = new FileInputStream(file).getChannel();
   MappedByteBuffer byteBuffer = ch.map(FileChannel.MapMode.READ_ONLY, 0, file.length());
   messagedigest.update(byteBuffer);
         return messagedigest.digest();
二, 研究了一下MD5的C原始碼之後,發現其實HASH加密演算法都是分段計算的。再看看java.security.MessageDigest類的說明,原來提供了很好的方法,就是新建一個MD類,分段地把byte[]呼叫update(byte[])。等到了檔案末尾,再一次過呼叫digest()就可以返回想要的結果的。So cool!
        public static byte[] MD5File(String fileName) {
            byte[] buf = new byte[4096]; //這個byte[]的長度可以是任意的。
            MessageDigest md;
            boolean fileIsNull = true;

            try {
                FileInputStream fis = new FileInputStream(fileName);
                int len = 0;
                md = MessageDigest.getInstance("MD5");

                len = fis.read(buf);
                if (len > 0) {
                    fileIsNull = false;
                    while (len > 0){
                        md.update(buf, 0, len);
                        len = fis.read(buf);
                    }
                }
            } catch (Exception e) {
                return null;
            }

            if (fileIsNull)
                return null;
            else
                return md.digest();
        }

最後試了一下兩種方式的效率,再與linux內建的md5sum(C版本)對比,發現用FileChannel.map的效率並不高,而且檔案的長度超過Integer.MAX_VALUE之後,map方法就會丟擲異常,說檔案太大了…我倒^_^!!!
而我用分段讀取的方式挺好的,在檔案小的情況下跟md5sum差不多,中等大的(幾百M)檔案比C版本的md5sum慢幾秒鐘,而對於5G多的檔案,都是用了2分多鐘,這就差不多了!HOHO!
也試過將FileInputStream.read改為NIO中FileChannel.read,發現速度竟然慢了,鑑於java的IO用NIO重寫過了,所以我就只用普通的IO了,而且普通IO的介面更友好些,我個人覺得。

至於NIO中的記憶體對映,我用過後覺得真是一種雞肋。它的文件上說了,對於小檔案,由於建立檔案記憶體MAPPING這麼大件事又划不來,1G多的檔案才值得。而實際上,3G以上的檔案有很多的啊,至少我硬碟上就很多。但超過了int的值的它就支援不了!這樣……唉,適用範圍太狹窄了。而且它map了之後不會釋放資源的,我就試過除錯的時候,在eclipse裡,run了這程式兩次,就宕機了,這是它不釋放這個mapping資源而耗盡了我4G的記憶體。唉,這個東西,慎用!