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数组为:[B@39ba5a14

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()函数,因为B5是16进制数,n取16,此处得到的数应该是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有负数的现象

原码, 反码, 补码 详解

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

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

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

  • 11
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值