Java計算檔案的hash值
阿新 • • 發佈:2019-02-14
如何知道一個檔案是否改變了呢?當然是用比較檔案hash值的方法,檔案hash又叫檔案簽名,檔案中哪怕一個bit位被改變了,檔案hash就會不同。
比較常用的檔案hash演算法有MD5和SHA-1。
我用的是MD5演算法,java中,計算MD5可以用MessageDigest這個類。
執行結果如下圖:
比較常用的檔案hash演算法有MD5和SHA-1。
我用的是MD5演算法,java中,計算MD5可以用MessageDigest這個類。
下面提供兩個工具類(任選其一即可)
第一個工具類:
程式碼如下:
package com.test; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; import java.math.BigInteger; import java.security.MessageDigest; public class MD5Util { public static void main(String[] args) { try { //此處我測試的是我本機jdk原始碼檔案的MD5值 String filePath = "C:\\Program Files\\Java\\jdk1.7.0_45\\src.zip"; String md5Hashcode = md5HashCode(filePath); String md5Hashcode32 = md5HashCode32(filePath); System.out.println(md5Hashcode + ":檔案的md5值"); System.out.println(md5Hashcode32+":檔案32位的md5值"); //System.out.println(-100 & 0xff); } catch (FileNotFoundException e) { e.printStackTrace(); } } /** * 獲取檔案的md5值 ,有可能不是32位 * @param filePath 檔案路徑 * @return * @throws FileNotFoundException */ public static String md5HashCode(String filePath) throws FileNotFoundException{ FileInputStream fis = new FileInputStream(filePath); return md5HashCode(fis); } /** * 保證檔案的MD5值為32位 * @param filePath 檔案路徑 * @return * @throws FileNotFoundException */ public static String md5HashCode32(String filePath) throws FileNotFoundException{ FileInputStream fis = new FileInputStream(filePath); return md5HashCode32(fis); } /** * java獲取檔案的md5值 * @param fis 輸入流 * @return */ public static String md5HashCode(InputStream fis) { try { //拿到一個MD5轉換器,如果想使用SHA-1或SHA-256,則傳入SHA-1,SHA-256 MessageDigest md = MessageDigest.getInstance("MD5"); //分多次將一個檔案讀入,對於大型檔案而言,比較推薦這種方式,佔用記憶體比較少。 byte[] buffer = new byte[1024]; int length = -1; while ((length = fis.read(buffer, 0, 1024)) != -1) { md.update(buffer, 0, length); } fis.close(); //轉換並返回包含16個元素位元組陣列,返回數值範圍為-128到127 byte[] md5Bytes = md.digest(); BigInteger bigInt = new BigInteger(1, md5Bytes);//1代表絕對值 return bigInt.toString(16);//轉換為16進位制 } catch (Exception e) { e.printStackTrace(); return ""; } } /** * java計算檔案32位md5值 * @param fis 輸入流 * @return */ public static String md5HashCode32(InputStream fis) { try { //拿到一個MD5轉換器,如果想使用SHA-1或SHA-256,則傳入SHA-1,SHA-256 MessageDigest md = MessageDigest.getInstance("MD5"); //分多次將一個檔案讀入,對於大型檔案而言,比較推薦這種方式,佔用記憶體比較少。 byte[] buffer = new byte[1024]; int length = -1; while ((length = fis.read(buffer, 0, 1024)) != -1) { md.update(buffer, 0, length); } fis.close(); //轉換並返回包含16個元素位元組陣列,返回數值範圍為-128到127 byte[] md5Bytes = md.digest(); StringBuffer hexValue = new StringBuffer(); for (int i = 0; i < md5Bytes.length; i++) { int val = ((int) md5Bytes[i]) & 0xff;//解釋參見最下方 if (val < 16) { /** * 如果小於16,那麼val值的16進位制形式必然為一位, * 因為十進位制0,1...9,10,11,12,13,14,15 對應的 16進製為 0,1...9,a,b,c,d,e,f; * 此處高位補0。 */ hexValue.append("0"); } //這裡藉助了Integer類的方法實現16進位制的轉換 hexValue.append(Integer.toHexString(val)); } return hexValue.toString(); } catch (Exception e) { e.printStackTrace(); return ""; } } /** * 方法md5HashCode32 中 ((int) md5Bytes[i]) & 0xff 操作的解釋: * 在Java語言中涉及到位元組byte陣列資料的一些操作時,經常看到 byte[i] & 0xff這樣的操作,這裡就記錄總結一下這裡包含的意義: * 1、0xff是16進位制(十進位制是255),它預設為整形,二進位制位為32位,最低八位是“1111 1111”,其餘24位都是0。 * 2、&運算: 如果2個bit都是1,則得1,否則得0; * 3、byte[i] & 0xff:首先,這個操作一般都是在將byte資料轉成int或者其他整形資料的過程中;使用了這個操作,最終的整形資料只有低8位有資料,其他位數都為0。 * 4、這個操作得出的整形資料都是大於等於0並且小於等於255的 */ }
執行結果如下圖:
第二個工具類:
結果一定為32位的,具體可看程式碼,程式碼如下:
package com.test; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; /** * Md5校驗工具類 */ public class MD5Util2 { private static final char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; public static void main(String[] args) { //此處我測試的是我本機jdk原始碼檔案的MD5值 String filePath = "C:\\Program Files\\Java\\jdk1.7.0_45\\src.zip"; String md5Hashcode2 = MD5Util2.getFileMD5(new File(filePath)); System.out.println("MD5Util2計算檔案md5值為:" + md5Hashcode2); System.out.println("MD5Util2計算檔案md5值的長度為:" + md5Hashcode2.length()); } /** * Get MD5 of a file (lower case) * @return empty string if I/O error when get MD5 */ public static String getFileMD5( File file) { FileInputStream in = null; try { in = new FileInputStream(file); FileChannel ch = in.getChannel(); return MD5(ch.map(FileChannel.MapMode.READ_ONLY, 0, file.length())); } catch (FileNotFoundException e) { return ""; } catch (IOException e) { return ""; } finally { if (in != null) { try { in.close(); } catch (IOException e) { // 關閉流產生的錯誤一般都可以忽略 } } } } /** * MD5校驗字串 * @param s String to be MD5 * @return 'null' if cannot get MessageDigest */ private static String getStringMD5( String s) { MessageDigest mdInst; try { // 獲得MD5摘要演算法的 MessageDigest 物件 mdInst = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); return ""; } byte[] btInput = s.getBytes(); // 使用指定的位元組更新摘要 mdInst.update(btInput); // 獲得密文 byte[] md = mdInst.digest(); // 把密文轉換成十六進位制的字串形式 int length = md.length; char str[] = new char[length * 2]; int k = 0; for (byte b : md) { str[k++] = hexDigits[b >>> 4 & 0xf]; str[k++] = hexDigits[b & 0xf]; } return new String(str); } @SuppressWarnings("unused") private static String getSubStr( String str, int subNu, char replace) { int length = str.length(); if (length > subNu) { str = str.substring(length - subNu, length); } else if (length < subNu) { // NOTE: padding字元填充在字串的右側,和伺服器的演算法是一致的 str += createPaddingString(subNu - length, replace); } return str; } private static String createPaddingString(int n, char pad) { if (n <= 0) { return ""; } char[] paddingArray = new char[n]; Arrays.fill(paddingArray, pad); return new String(paddingArray); } /** * 計算MD5校驗 * @param buffer * @return 空串,如果無法獲得 MessageDigest例項 */ private static String MD5(ByteBuffer buffer) { String s = ""; try { MessageDigest md = MessageDigest.getInstance("MD5"); md.update(buffer); byte tmp[] = md.digest(); // MD5 的計算結果是一個 128 位的長整數, // 用位元組表示就是 16 個位元組 char str[] = new char[16 * 2]; // 每個位元組用 16 進製表示的話,使用兩個字元, // 所以表示成 16 進位制需要 32 個字元 int k = 0; // 表示轉換結果中對應的字元位置 for (int i = 0; i < 16; i++) { // 從第一個位元組開始,對 MD5 的每一個位元組 // 轉換成 16 進位制字元的轉換 byte byte0 = tmp[i]; // 取第 i 個位元組 str[k++] = hexDigits[byte0 >>> 4 & 0xf]; // 取位元組中高 4 位的數字轉換, >>>, // 邏輯右移,將符號位一起右移 str[k++] = hexDigits[byte0 & 0xf]; // 取位元組中低 4 位的數字轉換 } s = new String(str); // 換後的結果轉換為字串 } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return s; } }
執行結果如下圖:
兩個工具類的對比:
測試程式碼如下:
執行結果如下圖:package com.test; import java.io.File; import java.io.FileNotFoundException; public class test { public static void main(String[] args) { try { //此處我測試的是我本機jdk原始碼檔案的MD5值 String filePath = "C:\\Program Files\\Java\\jdk1.7.0_45\\src.zip"; long start = System.currentTimeMillis(); String md5Hashcode = MD5Util.md5HashCode(filePath); long end = System.currentTimeMillis(); String md5Hashcode2 = MD5Util2.getFileMD5(new File(filePath)); long end1 = System.currentTimeMillis(); System.out.println("MD5Util 計算檔案md5值為:" + md5Hashcode + " --useTime:"+(end-start)); System.out.println("MD5Util2計算檔案md5值為:" + md5Hashcode2 + " --useTime:"+(end1-end)); } catch (FileNotFoundException e) { e.printStackTrace(); } } }
結論:
對比用時,建議使用第二個工具類!
番外篇:
其實還有一個重點,就是如何知道自己生成的MD5值是否正確呢?
方法很多,其實有一個挺簡單的方法,不需要另外安裝什麼軟體。
使用windows自帶的命令即可:certutil -hashfile [檔案路徑] MD5,
例子如下: