您的位置:首页 > 编程语言 > Java开发

Java中byte与16进制字符串的互换原理

2016-02-02 22:33 281 查看
我们都知道Java中的byte是由8个bit组成的,而16进制即16中状态,它是由4个bit来表示的,因为24=16。所以我们可以把一个byte转换成两个用16进制字符,即把高4位和低4位转换成相应的16进制字符,并组合这两个16进制字符串,从而得到byte的16进制字符串。同理,相反的转换也是将两个16进制字符转换成一个byte。转换的函数如下:

</pre><p><pre class="java" name="code">1./**
2. *  Convert byte[] to hex string
3. * @param src
4. * @return
5. */
6.public static String bytesToHexString(byte[] src){
7.	StringBuilder stringBuilder = new StringBuilder("");
8.	if(src==null||src.length<=0){
9.		return null;
10.	}
11.	for (int i = 0; i < src.length; i++) {
12.		int v = src[i] & 0xFF;
13.		String hv = Integer.toHexString(v);
14.		if (hv.length() < 2) {
15.			stringBuilder.append(0);
16.		}
17.		stringBuilder.append(hv);
18.	}
19.	return stringBuilder.toString();
20.}
21.
22./**
23. * Convert hex string to byte[]
24. * @param hexString
25. * @return
26. */
27.public static byte[] hexStringToBytes(String hexString) {
28.    if (hexString == null || hexString.equals("")) {
29.        return null;
30.    }
31.    hexString = hexString.toUpperCase();
32.    int length = hexString.length() / 2;
33.    char[] hexChars = hexString.toCharArray();
34.    byte[] d = new byte[length];
35.    for (int i = 0; i < length; i++) {
36.        int pos = i * 2;
37.        d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));
38.    }
39.    return d;
40.}
41.
42./**
43. * Convert char to byte
44. * @param c
45. * @return
46. */
47.private static byte charToByte(char c) {
48.    return (byte) "0123456789ABCDEF".indexOf(c);
49.}


bytesToHexString方法中src[i] & 0xFF将一个byte和0xFF进行了与运算,然后使用Integer.toHexString取得了十六进制字符串,可以看出src[i] & 0xFF运算后得出的仍然是个int,那么为何要和0xFF进行与运算呢?直接 Integer.toHexString(src[i]);,将byte强转为int不行吗?答案是不行的.

其原因在于:

1. byte的大小为8bits而int的大小为32bits;

2. java的二进制采用的是补码形式;

如果还不明白,我们还是温习下计算机基础理论和Java的位运算知识吧。

原码、反码和补码

计算机中的符号数有三种表示方法,即原码、反码和补码。三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位,三种表示方法各不相同。

• 原码表示法是机器数的一种简单的表示法。其符号位用0表示正号,用1表示负号,数值一般用二进制形式表示。设有一数为x,则原码表示可记作[x]原。

例如

X1= +1010110

X2= -1001010

其原码记作:

[X1]原=[+1010110]原=01010110

[X2]原=[-1001010]原=11001010

• 机器数的反码可由原码得到。如果机器数是正数,则该机器数的反码与原码一样;如果机器数是负数,则该机器数的反码是对它的原码(符号位除外)各位取反而得到的。设有一数X,则X的反码表示记作[X]反。

例如

X1= +1010110

X2= -1001010

[X1]原=01010110

[X1]反=[X1]原=01010110

[X2]原=11001010

[X2]反=10110101

• 机器数的补码可由原码得到。如果机器数是正数,则该机器数的补码与原码一样;如果机器数是负数,则该机器数的补码是对它的原码(除符号位外)各位取反,并在未位加1而得到的。设有一数X,则X的补码表示记作[X]补。

例如

[X1]=+1010110

[X2]=-1001010

[X1]原=01010110

[X1]补=01010110



[X1]原=[X1]补=01010110

[X2]原= 11001010

[X2]补=10110101+1=10110110

为何要使用原码, 反码和补码

byte是一个字节保存的,有8个位,即8个0、1。8位的第一个位是符号位, 也就是说0000 0001代表的是数字1,而1000 0000代表的就是-128,所以正数最大位0111 1111,也就是数字127 负数最大为10000000,也就是数字-128。

这里 0 是 00000000 ,而 10000000 是-128 ,正数计算里面去掉了一个0,所有最大值只能是2^7 -1 =127;而负数并没有用去掉0,所以是-128 。

现在我们知道了计算机可以有三种编码方式表示一个数。 对于正数因为三种编码方式的结果都相同:

[+1] = [00000001]原 = [00000001]反 = [00000001]补

所以不需要过多解释, 但是对于负数:

[-1] = [10000001]原 = [11111110]反 = [11111111]补

可见原码,,反码和补码是完全不同的。 既然原码才是被人脑直接识别并用于计算表示方式,为何还会有反码和补码呢?

首先, 因为人脑可以知道第一位是符号位,在计算的时候我们会根据符号位,选择对真值区域的加减(真值的概念在本文最开头)。但是对于计算机,加减乘数已经是最基础的运算,要设计的尽量简单。计算机辨别"符号位"显然会让计算机的基础电路设计变得十分复杂! 于是人们想出了将符号位也参与运算的方法。我们知道,根据运算法则减去一个正数等于加上一个负数,即: 1-1 = 1 + (-1) = 0,所以机器可以只有加法而没有减法,这样计算机运算的设计就更简单了。

于是人们开始探索 将符号位参与运算,并且只保留加法的方法。首先来看原码:计算十进制的表达式: 1-1=0

1 - 1 = 1 + (-1) = [00000001]原 + [10000001]原 = [10000010]原 = -2

如果用原码表示,让符号位也参与计算,显然对于减法来说,结果是不正确的。这也就是为何计算机内部不使用原码表示一个数。为了解决原码做减法的问题,出现了反码:

1 - 1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原= [0000 0001]反 + [1111 1110]反 = [1111 1111]反 = [1000 0000]原 = -0

发现用反码计算减法,结果的真值部分是正确的。 而唯一的问题其实就出现在"0"这个特殊的数值上。 虽然人们理解上+0和-0是一样的,但是0带符号是没有任何意义的。 而且会有[0000 0000]原和[1000 0000]原两个编码表示0。

于是补码的出现,解决了0的符号以及两个编码的问题:

1-1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原 = [0000 0001]补 + [1111 1111]补 = [0000 0000]补=[0000 0000]原

这样0用[0000 0000]表示,而以前出现问题的-0则不存在了。而且可以用[1000 0000]表示-128:

(-1) + (-127) = [1000 0001]原 + [1111 1111]原 = [1111 1111]补 + [1000 0001]补 = [1000 0000]补

-1-127的结果应该是-128,在用补码运算的结果中,[1000 0000]补 就是-128。 但是注意因为实际上是使用以前的-0的补码来表示-128,所以-128并没有原码和反码表示。(对-128的补码表示[1000 0000]补算出来的原码是[0000 0000]原,这是不正确的)。

使用补码,不仅仅修复了0的符号以及存在两个编码的问题,而且还能够多表示一个最低数。 这就是为什么8位二进制,使用原码或反码表示的范围为[-127,+127],而使用补码表示的范围为[-128,127]。

因为机器使用补码,所以对于编程中常用到的32位int类型,可以表示范围是: [-231,231-1] 因为第一位表示的是符号位。而使用补码表示时又可以多保存一个最小值。

Java的位运算

位运算表达式由操作数和位运算符组成,实现对整数类型的二进制数进行位运算。位运算符可以分为逻辑运算符(包括~、&、|和^)及移位运算符(包括>>、<<和>>>)。

1. 左移位运算符(<<)能将运算符左边的运算对象向左移动运算符右侧指定的位数(在低位补0)。

2. “有符号”右移位运算符(>>)则将运算符左边的运算对象向右移动运算符右侧指定的位数。 “有符号”右移位运算符使用了“符号扩展”:若值为正,则在高位插入0;若值为负,则在高位插入1。

3. Java也添加了一种“无符号”右移位运算符(>>>),它使用了“零扩展”:无论正负,都在高位插入0。这一运算符是C或C++没有的。

4. 若对char,byte或者short进行移位处理,那么在移位进行之前,它们会自动转换成一个int,转换时使用“符号扩展规则”。

在进行位运算时,需要注意以下几点。   

1. >>>和>>的区别是:在执行运算时,>>>运算符的操作数高位补0,而>>运算符的操作数高位移入原来高位的值。

2. 右移一位相当于除以2,左移一位(在不溢出的情况下)相当于乘以2;移位运算速度高于乘除运算。   

3. 若进行位逻辑运算的两个操作数的数据长度不相同,则返回值应该是数据长度较长的数据类型。   

4. 按位异或可以不使用临时变量完成两个值的交换,也可以使某个整型数的特定位的值翻转。   

5. 按位与运算可以用来屏蔽特定的位,也可以用来取某个数型数中某些特定的位。   

6. 按位或运算可以用来对某个整型数的特定位的值置。

位运算符的优先级:~的优先级最高,其次是<<、>>和>>>,再次是&,然后是^,优先级最低的是|。

回顾

回顾上述问题:为什么在bytesToHexString方法中不直接把byte类型的src[i]强制转换成int使用?

因为:byte会转换成int时,对于负数,会做符号扩展,如byte的-1(即0xff),转换成int的-1会扩展成0xffffffff,显然这不是我们所需要的。而把0xffffffff与0xff做与运算就能把高24位清零,这才是我们需要的。

Java的MD5

有了上述的理论知识我们不能写出MD5的加密方法啦

1./**

2. * MD5加密

3. * @param oraginalStr

4. * @return

5. * @throws NoSuchAlgorithmException

6. */

7.public static String md5(String oraginalStr) throws NoSuchAlgorithmException{

8. MessageDigest md5=MessageDigest.getInstance("MD5");

9. md5.update(oraginalStr.getBytes());

10.

11. return bytesToHexString(md5.digest()).toUpperCase();

12.}

附录:位操作用途

位与运算的主要用途如下:

1. 清零:快速对某一段数据单元的数据清零,即将其全部的二进制位为0。例如整型数a=321对其全部数据清零的操作为a&0x0。

321= 0000 0001 0100 0001

& 0= 0000 0000 0000 0000

= 0000 0000 0000 0000

2. 获取一个数据的指定位。例如获得整型数a=321的低八位数据的操作为a&0xFF。

321= 0000 0001 0100 0001

& 0xFF= 0000 0000 1111 11111

= 0000 0000 0100 0001

获得整型数a的高八位数据的操作为a&0xFF00

3. 保留数据区的特定位。例如获得整型数a=的第7-8位(从0开始)位的数据:

321= 0000 0001 0100 0001

& 384= 0000 0001 1000 0000

= 0000 0001 0000 0000

位或运算的主要用途:设定一个数据的指定位。例如整型数a=321,将其低八位数据置为1的操作为a=a|0XFF。

321= 0000 0001 0100 0001

| 0XFF= 0000 0000 1111 1111

= 0000 0000 1111 1111

位异或运算的主要用途:

1. 定位翻转:设定一个数据的指定位,将1换为0,0换为1。例如整型数a=321,,将其低八位数据进行翻位的操作为a^0XFF

321= 0000 0001 0100 0001

^ 0XFF= 0000 0000 1111 1111

= 0000 0001 1011 1110

2. 数值交换:例如a=3,b=4,无须引入第三个变量,利用位运算即可实现数据交换: 1.int a=3,b=4;

2.System.out.println(a+","+b);

3.a=a^b;

4.b=b^a;

5.a=a^b;

6.System.out.println(a+","+b);

输出:

3,4

4,3

转自:http://www.oseye.net/user/kevin/blog/295 其中笔误处已更正


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: