背景
项目中需要接收到的字节数组中的某一个字节(16进制) 转成 int , 但是这个字节是无符号的,一开始直接使用byte强转int(使用java语言),没想到遇到了大坑,同时也触及到了之前没有注意的一些关于java底层类型存储类型的设计,下面详细说明
byte转int引发的线上问题
项目(java语言)中会对接收到的字节数组进行解析,其中某一位的字节-16进制表示,表示一个数字。协议文档里也没写是否是有符号还是无符号的,只写了16进制表示的字节。因此想当然写下了如下的方法
ini
byte original = bytes[14];
int actValue = (int) original;
但是某一天 bytes[14] 突然传过来一个 0xA5 , 结果拿到的actValue 却是 -91 ,而原本数据源端想传过来的是 165, 从而导致了一次事故。事后进行分析复盘才发现,一个小小的byte转int,在java中涉及到蛮多之前不曾注意到的细节,这种对细节的处理,也让我对网络编程序列化等有了更多自己的理解
如何正确处理byte转int
一般而言,java中的基本数据类型都是分为有符号和无符号之分的,
比如对于0xA5这个字节来说,如何要解析成
-
有符号的byte , 则 0xA5 最高位就要被看做符号位,因此真实数值就是 -91
-
方式一: 原码:10100101 取反:01011010 (按位取反) 加1: 01011011 (加1得到绝对值) ,计算得到91 ,因为是负数得到-91
-
方式二:
-
- 对整个二进制数(
1010 0101
)减 1 :1010 0101
-1
=1010 0100
2. 对结果进行按位取反 :1010 0100
->0101 1011
3. 将0101 1011
转换为十进制: *0*128 + 1*64 + 0*32 + 1*16 + 1*8 + 0*4 + 1*2 + 1*1
*= 64 + 16 + 8 + 2 + 1 = 91
4. 因为原数是负数,所以结果是 -91。
- 对整个二进制数(
-
-
-
对于无符号的byte , 则 0xA5 全都看成数值,则是 165
因为java本身默认的就是有符号的,所以这种方式就不说了,下面看下如何把byte转成无符号的数值,因为java默认有符号,所以165超出了byte能承接的范围,此时只能通过short或者int来承接了。正确的方式如下
ini
byte original = bytes[14];
int actValue = original & 0xFF ;
就能得到正确的值165了。但是仔细分析下这个代码,不禁有几个疑问
为什么和 0xFF 做与运行就可以了,我们知道0xFF 也就是 8位1 ,这样可以保留 original 字节所有位的1,但是这和 original原本没有任何区别呀,为什么最后就能正确处理了呢 ?
这里发生了什么呢?
b
先被符号扩展为 0xFFFFFF A5
(-91)
0xFF
是 0x000000FF
按位与操作:
scss
11111111 11111111 11111111 10100101 (b扩展后)
00000000 00000000 00000000 11111111 (0xFF)
────────────────────────────────────
00000000 00000000 00000000 10100101 (结果:165)
问题又来了,为什么 0xFF 展开就变成了 00000000 00000000 00000000 11111111 ,而不是高位补1 而是补 0 呢 ?
这涉及到Java中字面量 类型 和自动类型提升的机制。
关键区别: 字面量 vs 变量
- 0xFF 是 int 类型字面量
arduino
0xFF // 这是一个 int 类型的字面量,值为 255
在Java中:
- 整数字面量(如
0xFF
、255
)默认是int
类型 int
是32位有符号整数0xFF
= 255,在32位中表示为:00000000 00000000 00000000 11111111
- byte变量需要符号扩展
ini
byte b = (byte)0xA5; // b 是 byte 类型变量
int result = b & 0xFF;
当进行 b & 0xFF
运算时:
第一步:操作数提升
b
(byte)被符号扩展为 int:11111111 11111111 11111111 10100101
0xFF
(int字面量)保持:00000000 00000000 00000000 11111111
第二步:按位与运算
scss
11111111 11111111 11111111 10100101 (b扩展后)
00000000 00000000 00000000 11111111 (0xFF)
────────────────────────────────────
00000000 00000000 00000000 10100101 (结果)
为什么0xFF不需要符号扩展?
因为 0xFF
(255)对于 int 类型来说是正数:
csharp
int value = 0xFF; // 255,正数
System.out.println(Integer.toBinaryString(0xFF));
// 输出: 11111111 (实际是32位,高位都是0)
总结
本文通过一次byte转int来说明java中数据类型的底层存储方式,以及在转换时java在自动变量提升时所做的一些转换,通过探究背后的工作从而对数据的底层存储有了更深入的认识和了解