占用字节数 & 取值范围
类型 | 存储要求 | 范围 | 默认值 | 包装类 |
---|---|---|---|---|
byte | 1字节 | -128~127 (-2^7 ~ 2^7-1 ) | 0 | Byte |
short | 2字节 | -32768~32767(-2^15 ~ 2^15 -1) | 0 | Short |
int | 4字节 | -2147483648~ 2147483647 (-2^31~ 2^31-1 ) | 0 | Integer |
long | 8字节 | -263~263-1 | 0 | Long |
float | 4字节 | -3.4e+38 ~ 3.4e+38 | 0.0f | Float |
double | 8字节 | -1.7e+308 ~ 1.7e+308 | 0 | Double |
char | 2字节 | ‘0’ | Character | |
boolean | 1字节 | false | Boolean |
原码、反码、补码
原码 | 反码 | 补码 | |
---|---|---|---|
正数 | 本身 | 本身 | 本身 |
负数 | 本身 | 在原码的基础上, 符号位不变,其余各个位取反 | 在反码的基础上加1 |
原码:最高位为符号位,0代表正数,1代表负数,非符号位为该数字绝对值的二进制表示。
反码:正数的反码与原码一致;负数的反码是只是最高位(符号位)不变 ,对原码按位取反。
补码:正数的补码与原码一致;负数的补码是该数的反码加1。
进行0xFF操作的主要目的是 保证补码的一致性,也可以说成是去符号操作。补充一个知识点,Java 是使用补码进行运算和储存数据的,至于为什么要用补码本文不做过多介绍,感兴趣的可以自行研究下。
public static void main(String[] args) {
-127 原码 1111 1111
反码 1000 0000
补码 1000 0001
byte b = -127;
int a = b;
System.out.println(a);//输出结果-127
a = b & 0xff;
System.out.println(a);//输出结果 129
}
Java的存储都是按照补码存储的
b = -127 补码表示为 1000 0001
int a = b; 这行代码是将 byte 类型传换为int类型,计算机会自动补位,正数自动补位高位全部补0,负数自动补位高位全部补1,这是规定,这里b为负数,转为int类型是4个字节,一个字节8位,补码提升为 32位,补码的高位补1,也就是
1111 1111 1111 1111 1111 1111 1000 0001(补码)
负数的补码转为原码,符号位不变,减1后 ,再其他位取反,正数的补码,反码都是本身,结果是
1000 0000 0000 0000 0000 0000 0111 1111(原码) 表示为十进制 也是 -127
也就是 当 byte -> int 能保证十进制数不变,这里就是第一个System.out.println 输出-127的原因。
再看看 b & 0xff 之后为什么就输出 129?
b = -127 补码表示为 1000 0001
代码 a = b & 0xff,a 是 int 类型 , b 是 byte类型,这里虚拟机首先是 把 b 转换为 int 类型 后 再执行 &0xFF的,不要问为什么是先转换成int 类型 再 执行 &0xFF 而不是 先执行&0xFF 再转成int 类型的,这是规则,计算机就是这样设计的,将 b 转换成int 类型 ,b 为负数,高位必将补1 ,此时补码显然发生变化,再与上0xff,将高24重新置0,这样能保证补码的一致性,当然由于符号位发生变化,表示的十进制数就会变了,变为129
1111 1111 1111 1111 1111 1111 1000 0001( -127 int 类型 补码)
此时补码显然已经发生变化,执行& 0xff 过程如下:
1111 1111 1111 1111 1111 1111 1000 0001(-127 int 类型 补码)
&
0000 0000 0000 0000 0000 0000 1111 1111(0xFF)
一一一一一一一一一一一一一一一一一一一一一一一
0000 0000 0000 0000 0000 0000 1000 0001(补码)
这里&0xFF之后虽然还是补码,但高位已经变成0了,也就是说操作之后的结果为正数,正数的原码,反码,补码都一致,所以 &0xFF 之后 System.out.println 输出 129(0000 0000 0000 0000 0000 0000 1000 0001)
经过上面分析 &0xFF 确实可以做到保证补码的一致性,那再思考下为什么要保证补码一致性,从串口上来的数据传到Java后不执行 &0xFF会怎么样?
- 假设MCU发送数据 100 到Java层
十进制 | 原码 | 反码 | 补码 |
---|---|---|---|
100 | 0110 0100 | 0110 0100 | 0110 0100 |
这里补充说下,大部分公司 MCU和Java层串口通讯都是无符号的数据,也就是说一个字节表示 0~255。
100 在MCU那边无符号的二进制是 0110 0100,Java层接收到数据 必然是 0110 0100,不执行 &0xFF , 0110 0100高位为0,表示正数,补位后如下
0000 0000 0000 0000 0000 0000 0110 0100(补码)
正数的原码反码和补码都是一样的,所以直接转换成int 类型 十进制还是100,咋一看没啥问题,貌似不执行&0xFF 也没啥问题?
- 假设MCU发送数据 132 到Java层
十进制 | 原码 | 反码 | 补码 |
---|---|---|---|
132 | 1000 0100 | 1111 1011 | 1111 1110 |
132在MCU那边无符号的二进制是 1000 0100,Java层接收到数据必然是 1000 0100,然后储存起来,前面说了Java是以补码的形式存储数据的,也就是说在Java层 1000 0100被看成是补码,如果不执行 &0xFF, 补位如下:
1111 1111 1111 1111 1111 1111 1000 0100(补码)
转换成原码如下:
1000 0000 0000 0000 0000 0000 0111 1100(原码) 表示为十进制 为 -124。
也就是说MCU 本来想发送 132 到 Java层,但是Java层收到的是 -124。
结论
当MCU发送的数据大于等于128 时,Java层必须要进行 0xFF 操作,否侧收到的数据与MCU的初始值有出入。
java中基本类型从小扩展到大的数据类型时候,正数因为符号位是0,无论如何都是补零扩展,但是负数补零扩展和补符号位扩展完全不同。
负数补符号位扩展:保证十进制数不变,例如 byte>>>int -127自动按照补符号位扩展,在高24位补符号位1,表示的十进制数不变。
负数补零扩展:保证补码的一致性,但是表示的十进制发生变化例如,本例中byte提升为int,&0xff的操作。