1. 程式人生 > >關於Java二進位制和十六進位制轉化的一點問題

關於Java二進位制和十六進位制轉化的一點問題

    今天在學習Spring Security在做密碼加密配置時,有個地方需要做一個二進位制和十進位制的轉化,自己便隨手寫了一個,沒想到,在運行了幾次之後始終驗證不過,鬱悶的不行,由於我用的是Spring Security框架的驗證策略,所以一時間也不知道錯在什麼地方,於是將原始碼匯入,debug一下發現,對密碼的md5加密結果比對失敗,這才意識到有可能是在對MD5摘要時進行二進位制轉十六進位制時出的問題,下面是我隨手寫的一個二進位制轉換十六進位制的方法:

public static void main(String[] args) throws Exception {
		MessageDigest digest = MessageDigest.getInstance("MD5");                
		byte[] bytes = digest.digest("admin".getBytes(Charset.forName("UTF8")));
		StringBuffer sb = new StringBuffer();                                   
		for(int i = 0; i < bytes.length; i++){                                  
			// 二進位制轉化成十進位制                                                 
			int l = 0xff & bytes[i]; 
			// 十進位制轉化成十六進位制
			sb.append(Integer.toHexString(l));
		}                                                                       
		System.out.println(sb.toString()); 
	}
執行結果:
21232f297a57a5a743894ae4a801fc3
感覺上沒什麼問題,但是執行的結果卻一直不對,於是在debug時,儲存了框架所生成的MD5摘要結果,一眼看過去似乎也沒問題,但是一對比發現,存在一個差別:

框架產生的:

21232f297a57a5a743894a0e4a801fc3
對比:
21232f297a57a5a743894ae4a801fc3  // 我的方法產生的
21232f297a57a5a743894a0e4a801fc3 // 框架輸出的
發現少了一個0

為什麼會少一個0呢,難道是我二進位制轉十進位制的時候,有問題嗎,還是JDK自帶的API在進行十進位制轉二進位制時存在缺陷呢?

於是將生成的二進位制打印出來結果如下:

100001 100011 101111 101001 1111010 1010111 10100101 10100111 1000011 10001001 1001010 1110 1001010 10000000 11111 11000011

// 轉化格式對比
10   0001     21
10   0011     23
10   1111     2f
10   1001     29
111  1010     7a
101  0111     57
1010 0101     a5
1010 0111     a7
100  0011     43
1000 1001     89
100  1010     4a
     1110     e
100  1010     4a
1000 0000     80
1    1111     1f
1100 0011     c3
發現當高四位均為0時,JDK自帶的API在轉十六進位制時好像不予處理了

通過檢視JDK的原始碼發現確實是這樣:

 final static char[] digits = {
	'0' , '1' , '2' , '3' , '4' , '5' ,
	'6' , '7' , '8' , '9' , 'a' , 'b' ,
	'c' , 'd' , 'e' , 'f' , 'g' , 'h' ,
	'i' , 'j' , 'k' , 'l' , 'm' , 'n' ,
	'o' , 'p' , 'q' , 'r' , 's' , 't' ,
	'u' , 'v' , 'w' , 'x' , 'y' , 'z'
    };
    private static String toUnsignedString(int i, int shift) { // 轉16進位制時 shift=4
	char[] buf = new char[32];
	int charPos = 32;
	int radix = 1 << shift;
	int mask = radix - 1;
	do {
	    buf[--charPos] = digits[i & mask]; // 這裡相當於是 i&0x0f 取的低四位
	    i >>>= shift;//取高四位,此時當i=0時,迴圈即結束,所以當高四位均為0時,被忽略掉了

	} while (i != 0);

	return new String(buf, charPos, (32 - charPos));
    }

這裡不知道是不是與無符號有關,我對Java的無符號型資料型別,相對陌生。

反正按照上面的方法進行二進位制轉十六進位制,是有問題的,所以還是用那種比較穩妥的方法,自己來取高四位和低四位分別來進行處理,程式碼如下(思路都是一樣的,先去高四位,然後再去低四位,分別進行處理,我這裡直接找來了框架的原始碼)貼上如下:

public class Hex {
	private static final char[] HEX = {
	        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
	    };
        public static char[] encode(byte[] bytes) {
	        final int nBytes = bytes.length;
	        char[] result = new char[2*nBytes];
	        int j = 0;
	        for (int i=0; i < nBytes; i++) {
	        	// Char for top 4 bits
	            result[j++] = HEX[(0xF0 & bytes[i]) >>> 4 ];
	            // Bottom 4
	            result[j++] = HEX[(0x0F & bytes[i])];
	        }
	        return result;
	    }
}