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的内存。唉,这个东西,慎用!

你可能感兴趣的:(exception,加密,String,File,null,byte)