Java中16进制数与Byte的相互转换及其相关
最近研究Java中的Socket,发现16进制与Byte数据相互转换的函数在Socket中非常常见,并且其中还有非常多值得深究的点,故写下此篇文章。欢迎各位一起探讨。
1.发送和接收
发送:将16进制的String字符串,转换成Byte数组,并发送
接收:接收传输过来的Byte数组,将其转换成16进制的String字符串
这里以“B5 5B 01 09 04”这个16进制字符串为例
假设我们发送的字符串为“B5 5B 01 09 04”,那么
发送的全部过程为:Java接收“B5 5B 01 09 04”这个字符串,将其转换成Byte[],并将此Byte数组通过输出流发送到服务端Server。
接收的全部过程为:输入流接收服务端Server传输的Byte[],本地客户端Client将此Byte数组还原为16进制字符串,并在Client输出此字符串。
其中Socket通信所使用的数据为Byte数组
因此,在客户端Client需要写出两个函数:
16进制字符串转Byte数组函数Hex2Byte
//16进制字符串转byte数组
public static byte[] Hex2Byte(String inHex) {
String[] hex=inHex.split(" ");//将接收的字符串按空格分割成数组
byte[] byteArray=new byte[hex.length];
for(int i=0;i<hex.length;i++) {
//parseInt()方法用于将字符串参数作为有符号的n进制整数进行解析
byteArray[i]=(byte)Integer.parseInt(hex[i],16);
}
return byteArray;
}
Byte数组转16进制字符串函数Byte2Hex
//byte数组转16进制字符串
public static String Byte2Hex(byte[] inByte) {
StringBuilder sb=new StringBuilder();
String hexString;
for(int i=0;i<inByte.length;i++) {
//toHexString方法用于将16进制参数转换成无符号整数值的字符串
String hex=Integer.toHexString(inByte[i]);
if(hex.length()==1) {
sb.append("0");//当16进制为个位数时,在前面补0
}
sb.append(hex);//将16进制加入字符串
sb.append(" ");//16进制字符串后补空格区分开
}
hexString=sb.toString();
hexString=hexString.toUpperCase();//将16进制字符串中的字母大写
return hexString;
}
2.问题
假设我们发送的字符串和接收的字符串一样,均为“B5 5B 01 09 04”。
写完程序,运行之后的结果如下:
请输入通讯报文:
B5 5B 01 09 04
Server返回的结果为:
FFFFFFB5 5B 01 09 04
可以发现,第一个数本该是B5,最终却成了FFFFFFB5,而其他的数却没有问题,这是为什么呢?
截取客户端发送数据之前,将16进制转换成Byte数组,还未发送之前的数据,发现:
请输入通讯报文:
B5 5B 01 09 04
发送前的Byte数组为:[[email protected]
Server返回的结果为:
FFFFFFB5 5B 01 09 04
直接将Byte数组输出,会得到一串乱码。
此处写一个for循环,将Byte[]中的元素按次序输出,结果为:
请输入通讯报文:
B5 5B 01 09 04
Byte数组各项为:
-75 91 1 9 4
Server返回的结果为:
FFFFFFB5 5B 01 09 04
第一个数为负数,而后4个数为正数,Server返回的结果中错的也是第一个数“B5”。
我们输入16进制数B5的时候,对应的10进制数应该是181,为一个正数,可转换为Byte数据的时候却成了负数-75,不是我们预想的181。
弄清为什么转换的时候会变成负数,之前的问题也会迎刃而解。
3.Java中Int与Byte数据转换的问题
在之前的16进制转Byte数据函数Hex2Byte中,调用了parseInt()方法,此方法是将字符串参数作为有符号的n进制整数进行解析。
在我们输入的“B5 5B 01 09 04”字符串中,将第一个字符串“B5”代入parseInt()函数,因为算的是十位数,n取10,此处得到的数应该是181,是一个int型数据。
Int型数据由32个bit,也就是32个0或1组成。
将int 181按bit展开得到:
00000000 00000000 00000000 10110101
Hex2Byte函数将字符串所代表的16进制数转换成Byte,而Byte是8bit,也就是由8个0或1组成。
16进制数B5的Byte数为:
10110101
Int转换为Byte的过程,也是将Int里32个bit的前24个“砍掉”,只留下最后8个bit的过程。
Byte里第一位为符号位,0为正,1为负,且负数均用补码表示。
对此Byte数取反,再加1,得到
10110101(补)=11001011(原)= -75 (10进制)
-75即为我们发送的字符串“B5”的Byte值。
由于16进制中2个字符最大可以表示为FF,换算成二进制为11111111,8个1,所以int数据中前24个必然为0。
计算机在发送数据时,会将32bit的int型数据前面的24个0“砍掉”,转换为Byte数据,而Byte的第一位为符号位,当第一位为1时,计算机会认为此Byte数为负数,第一位为1的数对应10进制数则为大于128的数。
用127 128 129(16进制分别为7F 80 81)验证一下:
请输入通讯报文:
7F 80 81
Byte数组各项为:
127 -128 -127
Server返回的结果为:
7F FFFFFF80 FFFFFF81
其实,在Socket发送的过程中,无论我们认为这个数是B5也好,还是-75也好,计算机发送过去的都是“10110101”这一串Byte数,接收到的也会是类似的Byte。
真正的问题,出在解析接收回来的Byte数据过程中。
Java在使用toHexString方法,将16进制Byte转换成Int数据的时候,如果Byte第一位为1,Java会认为此数为负数,并做位扩展。例如Byte的-1会被换成int的-1
Byte:-1=11111111(补)=0xFF(16进制)
Int:-1=11111111 11111111 11111111 11111111(补)=0xFFFFFFFF(16进制)
至此,我们终于找到了问题的根源。
4.解决方法
当返回的Byte数据第一位符号位为-1时,Java会做位扩展,补上24个1,扩展为一个32位的Int数。
而我们需要的只是Byte数据的最后8位,前面的24个数全取0即可,因此我们在将Byte数转换位Int数的时候,可以将转换后得到的Int数与0xFF做与运算,消掉前面的1。
B5(16进制)=181(10进制)=10110101
转换后的0xFFFFFFB5 = 11111111 11111111 11111111 10110101 = -75
0xFF = 00000000 00000000 00000000 11111111
0xFF & 0xFFFFFFB5 = 00000000 00000000 00000000 10110101 = 181 = B5
修改后的代码,Byte数组转16进制字符串函数Byte2Hex为:
public static String Byte2Hex(byte[] inByte) {
StringBuilder sb=new StringBuilder();
String hexString;
for(int i=0;i<inByte.length;i++) {
//toHexString方法用于将16进制参数转换成无符号整数值的字符串
//与0xFF做与运算,消除byte符号位为负数带来的影响
String hex=Integer.toHexString(0xFF & inByte[i]);
if(hex.length()==1) {
sb.append("0");//当16进制为个位数时,在前面补0
}
sb.append(hex);//将16进制加入字符串
sb.append(" ");//16进制字符串后补空格区分开
}
hexString=sb.toString();
hexString=hexString.toUpperCase();//将16进制字符串中的字母大写
return hexString;
}
修改后程序运行结果如下:
请输入通讯报文:
B5 5B 01 09 04
Server返回的结果为:
B5 5B 01 09 04
至此,问题解决。
5.一些拓展。
当我们转换B5为Byte数的时候,计算机会认为这个数是-75,而我们认为这个数是181。无论是-75还是181,Java发送Socket的Byte数据的时候均是二进制的10110101。
也就是说,计算机不管这个数是-75还是181,它只负责发送10110101,一串0和1而已。
对于二进制数10110101,当我们认为第一位是符号位,那它就是-75。当我们认为第一位不是符号位,那它就是181。那么这两个数有什么联系呢?
再找回之前的例子,多看几个数。
请输入通讯报文:
7F 80 81
Byte数组各项为:
127 -128 -127
Server返回的结果为:
7F FFFFFF80 FFFFFF81
这里我们发送的127 128 129三个数中,只看后两个数,可以看到128同时被认为是-128,而129同时被认为是-127。
通过观察,发现
|128| + |-128| = 256
|129| + |-127| = 256
再看之前的B5:
|-75| + |181| = 256
可以看出,两个数的绝对值相加等于256。这里面的规律,正是计算机原码,补码,反码的知识。
要讲清楚里面规律,可以再写一篇文章了,因此不再展开。
简而言之,计算机减去一个数,等于加上该数的同余数。
就和调整时钟一样,从0点调整到3点,可以顺时针转3小时,也可以逆时针转9小时。
|3| + |-9| = 12,正如|128| + |-128| = 256一样。
这里的12和256可以认为是时钟的一圈。
“调整时钟”确实是一个很生动的例子。
最后,引用一些个人觉得很不错的资料,有兴趣的各位可以自行观看。
【硬件科普】带你认识CPU第04期——CPU是怎么计算减法的
上一篇: CocosCreator控制物体移动
下一篇: 快速双边滤波——Python实现