论坛首页 编程语言技术论坛

Java 自带MD5 校验算法,别上当

浏览 47133 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2012-10-22   最后修改:2012-10-22

前天第一次发表博客到论坛,关于Java文件监控一文,帖子地址在:http://www.iteye.com/topic/1127281

评论的朋友很多,下载代码的朋友很不少,感谢在论坛上看我帖子的朋友,还有回复评论的朋友,给我提供建议的朋友。

 

从这些建议中,虽然语言简短,但是却有的是一语中的,这里说一下一下关于帖子的代码中HashFile中的MD5文件校验算法,

该算法是使用Java自带的MessageDigest类,测试结果,获取一个2G文件的MD5码,耗时 971秒,这效率太给力了,可以用坑爹来形容,所以用MD5文件校验码来判断文件是否被修改,对于小文件来说可能还合适,要是对大文件来说,好吧,撞墙死了算了!

 

HashFile中的代码是这样子的:

import java.io.FileInputStream;
import java.io.InputStream;
import java.security.MessageDigest;

public class HashFile {  
  
    /** 
     * @param args 
     */  
    public static char[] hexChar = { '0', '1', '2', '3', '4', '5', '6', '7',  
            '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; 
  
    public static String getHash(String fileName, String hashType)  
            throws Exception {  
        InputStream fis;  
        fis = new FileInputStream(fileName);  
        byte[] buffer = new byte[1024];  
        MessageDigest md5 = MessageDigest.getInstance(hashType);  
        int numRead = 0;  
        while ((numRead = fis.read(buffer)) > 0) {  
            md5.update(buffer, 0, numRead);  
        }  
        fis.close();  
        return toHexString(md5.digest());  
    }  
  
    public static String toHexString(byte[] b) {  
        StringBuilder sb = new StringBuilder(b.length * 2);  
        for (int i = 0; i < b.length; i++) {  
            sb.append(hexChar[(b[i] & 0xf0) >>> 4]);  
            sb.append(hexChar[b[i] & 0x0f]);  
        }  
        return sb.toString();  
    }  
}  

 

测试结果:


真给力啊,超过2G,效率变成这样 !

好吧,自带的MD5算法,上当了,对于检查文件是否更新这个问题来说,现在我使用的解决办法是File 类的lastModified方法,代码这样

	private String getHash(String fp){
		File file = new File(fp);
		return String.valueOf(file.lastModified());
	}

 通过比较文件的最后修改时间来判断文件是否更新,对大文件也轻松拿下,

测试结果是这样:



 当然针对不同问题肯定是有不同的解决办法

 

分析原来HashFile代码,获取MD5校验码的瓶颈是出现在

public static String getHash(String fileName, String hashType)  
            throws Exception {  
        InputStream fis;  
        fis = new FileInputStream(fileName);  
        byte[] buffer = new byte[1024];  
        MessageDigest md5 = MessageDigest.getInstance(hashType);  
        int numRead = 0;  
        while ((numRead = fis.read(buffer)) > 0) {  //瓶颈
            md5.update(buffer, 0, numRead);  
        }  
        fis.close();  
        return toHexString(md5.digest());  
    }  

 在上面代码中,while循环N次,2G的文件,循环1024 * 1024  * 2 次,不给力!

   发表时间:2012-10-22  
解决办法呢?
0 请登录后投票
   发表时间:2012-10-22  
superchinaren 写道
解决办法呢?

引用
好吧,自带的MD5算法,上当了,对于检查文件是否更新这个问题来说,现在我使用的解决办法是File 类的lastModified方法,代码这样

对于我之前的程序的解决办法就这样,其他的办法,可能需要另想办法,毕竟MD5文件校验那是需要经过对比每一个字节的,对于大文件来说,还是不建议使用MD5文件校验
0 请登录后投票
   发表时间:2012-10-22  
... ... 这是闹哪样没看明白。
2G的文件校检本来就有那么点慢来着,你去下个工具试试,当然,比起c/c++实现的是慢了不是一星半点。
但是你这个读文件的实现本来也木有充分利用到缓存嘛。
0 请登录后投票
   发表时间:2012-10-22  
来个nio的简单版,看你们老是怀疑java慢

C++ MD5工具验证结果:

File: K:\Games\World of Warcraft\Data\common.MPQ
Size: 2226587191 bytes
Modified: 2008年11月19日 星期三, 12:57:24
MD5: CD9F9C5523F3BA3866B81CCC74ED6476


java运行结果,毫秒
耗时:12672,cd9f9c5523f3ba3866b81ccc74ed6476


核心代码
                String hashType = "MD5";
		FileInputStream fStream = null;
		try {
			MessageDigest md5 = MessageDigest.getInstance(hashType);
			fStream = new FileInputStream(
					//"K:\\Games\\World of Warcraft\\Scan.dll");
					//"K:\\Games\\World of Warcraft\\Data\\patch-3.MPQ");
					"K:\\Games\\World of Warcraft\\Data\\common.MPQ");
			FileChannel fChannel = fStream.getChannel();
			ByteBuffer buffer = ByteBuffer.allocate(8*1024);
			long s = System.currentTimeMillis();
			for ( int count = fChannel.read( buffer ); count !=-1 ; count = fChannel.read( buffer )
				) {
				buffer.flip();
				md5.update( buffer );
				if( !buffer.hasRemaining() ){
					//System.out.println("count:"+count);
					buffer.clear();
				}
			}
			s = System.currentTimeMillis() - s;
			System.out.println( "耗时:"+s+","+getString( md5.digest() ) );
			
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}finally{
			try {
				if( fStream!=null )
					fStream.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
2 请登录后投票
   发表时间:2012-10-23  
chimer 写道
来个nio的简单版,看你们老是怀疑java慢

C++ MD5工具验证结果:

File: K:\Games\World of Warcraft\Data\common.MPQ
Size: 2226587191 bytes
Modified: 2008年11月19日 星期三, 12:57:24
MD5: CD9F9C5523F3BA3866B81CCC74ED6476


java运行结果,毫秒
耗时:12672,cd9f9c5523f3ba3866b81ccc74ed6476


核心代码
                String hashType = "MD5";
		FileInputStream fStream = null;
		try {
			MessageDigest md5 = MessageDigest.getInstance(hashType);
			fStream = new FileInputStream(
					//"K:\\Games\\World of Warcraft\\Scan.dll");
					//"K:\\Games\\World of Warcraft\\Data\\patch-3.MPQ");
					"K:\\Games\\World of Warcraft\\Data\\common.MPQ");
			FileChannel fChannel = fStream.getChannel();
			ByteBuffer buffer = ByteBuffer.allocate(8*1024);
			long s = System.currentTimeMillis();
			for ( int count = fChannel.read( buffer ); count !=-1 ; count = fChannel.read( buffer )
				) {
				buffer.flip();
				md5.update( buffer );
				if( !buffer.hasRemaining() ){
					//System.out.println("count:"+count);
					buffer.clear();
				}
			}
			s = System.currentTimeMillis() - s;
			System.out.println( "耗时:"+s+","+getString( md5.digest() ) );
			
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}finally{
			try {
				if( fStream!=null )
					fStream.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

看来是不一样的水平写的代码效率差的不是N多,呵呵...多谢指点
0 请登录后投票
   发表时间:2012-10-23   最后修改:2012-10-23
2G文件不是一次性读取,要访问外存,所以慢了。
2G已经不能说明问题了,要用3  4G的。
1 请登录后投票
   发表时间:2012-10-23  
somefuture 写道
2G文件不是一次性读取,要访问外存,所以慢了。
2G已经不能说明问题了,要用3  4G的。

哈哈,那得等到脖子比长颈鹿还长...
0 请登录后投票
   发表时间:2012-10-23  
两者似乎没多大的区别吧

public class MD5 {
 
 	public static char[] hexChar = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
 
 	public static String toHexString(byte[] b) {
 		StringBuilder sb = new StringBuilder(b.length * 2);
 		for (int i = 0; i < b.length; i++) {
 			sb.append(hexChar[(b[i] & 0xf0) >>> 4]);
 			sb.append(hexChar[b[i] & 0x0f]);
 		}
 		return sb.toString();
 	}
 
 	public static String getHash(String fileName, String hashType) throws Exception {
 		InputStream fis = new FileInputStream(fileName);
 		byte[] buffer = new byte[1024];
 		MessageDigest md5 = MessageDigest.getInstance(hashType);
 		int numRead = 0;
 		while ((numRead = fis.read(buffer)) > 0) { // 瓶颈
 			md5.update(buffer, 0, numRead);
 		}
 		fis.close();
 		return toHexString(md5.digest());
 	}
 
 	public static String getHashNio(String fileName, String hashType) throws Exception {
 		FileInputStream fStream = null;
 		String hash = null;
 		try {
 			MessageDigest md5 = MessageDigest.getInstance(hashType);
 			fStream = new FileInputStream(fileName);
 			FileChannel fChannel = fStream.getChannel();
 			ByteBuffer buffer = ByteBuffer.allocate(8 * 1024);
 			for (int count = fChannel.read(buffer); count != -1; count = fChannel.read(buffer)) {
 				buffer.flip();
 				md5.update(buffer);
 				if (!buffer.hasRemaining()) {
 					// System.out.println("count:"+count);
 					buffer.clear();
 				}
 			}
 			hash = toHexString(md5.digest());
 
 		} catch (NoSuchAlgorithmException e) {
 			e.printStackTrace();
 		} catch (FileNotFoundException e) {
 			e.printStackTrace();
 		} catch (IOException e) {
 			e.printStackTrace();
 		} finally {
 			try {
 				if (fStream != null)
 					fStream.close();
 			} catch (IOException e) {
 				e.printStackTrace();
 			}
 		}
 		return hash;
 	}
 
 	public static void main(String[] args) throws Exception {
 		String fileName = "G:/QQDownload/The.Man.From.Earth.2007.mkv";
 		long t1 = System.currentTimeMillis();
 
 		System.out.println(MD5.getHash(fileName, "MD5"));
 		long t2 = System.currentTimeMillis();
 		System.out.println("耗时:" + (t2 - t1) + " ms");
 
 		System.out.println(MD5.getHashNio(fileName, "MD5"));
 		System.out.println("耗时:" + (System.currentTimeMillis() - t2) + " ms");
 	}
 
 }


运行3次结果如下:
d1b3870825234c4fbf0f21993ad9e00d
耗时:25516 ms
d1b3870825234c4fbf0f21993ad9e00d
耗时:24578 ms

d1b3870825234c4fbf0f21993ad9e00d
耗时:27875 ms
d1b3870825234c4fbf0f21993ad9e00d
耗时:34344 ms

d1b3870825234c4fbf0f21993ad9e00d
耗时:29531 ms
d1b3870825234c4fbf0f21993ad9e00d
耗时:28781 ms


NIO的版本性能似乎没什么优势
0 请登录后投票
   发表时间:2012-10-23  
而且,两者之间的些微差别会不会仅仅只是Byte Buffer大小造成的
0 请登录后投票
论坛首页 编程语言技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics