欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java中16进制数与Byte的相互转换及其相关

程序员文章站 2024-03-16 23:05:52
...

最近研究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可以认为是时钟的一圈。

“调整时钟”确实是一个很生动的例子。

最后,引用一些个人觉得很不错的资料,有兴趣的各位可以自行观看。

谈谈java中字节byte有负数的现象

原码, 反码, 补码 详解

【硬件科普】带你认识CPU第04期——CPU是怎么计算减法的

【硬件通信】Java Socket怎么发送和接收16进制数据

Java中byte与16进制字符串的互相转换