Java byte位移操作 注意事项
Java对byte 的 + - * / >> >>> << & | ^ (加,减,乘,除,右移,左移,无符号右移,位与,位或,位异或)操作,均会是首先将byte转化为int, 再行运算。这一事实可能导致多种问题:
假设我们想进行如下byte运算: 1111 1000 右移1位,再与0000 0001 或运算,得 0111 1101。
直觉写程序如下:
byte b = 0xf8;
byte b2 = b >> 1 | 0x01;
这个写法里有多重错误,现逐个纠正:
1 编译器报错,int无法直接自动转化为byte
为解决此问题,加强制转化。
byte b = (byte)0xf8;
byte b2 = (byte)( (b >> 1) | 0x01);
2 输出为 1111 1101 不是我们想要的 0011 1101
原因是>> 是有符号右移,当符号位为1时,左侧补的是1而非0。
修改为使用 >>> 无符号右移:
byte b = (byte)0xf8;
byte b2 = (byte)( (b >>> 1) | 0x01);
3 运行后发现输出依然为 1111 1101
原因是byte在运算前先转化为int再行位运算,因此分解后的运算步骤如下:
b 转化为int 1111 1000 转化为 11111111 11111111 11111111 11111000
无符号右移1位 01111111 11111111 11111111 11111100
与 0x01 按位或 01111111 11111111 11111111 11111101
强制转化回byte 11111101
解决方案,在右移运算前先 位与 0xff
byte b = (byte)0xf8;
byte b2 = (byte)( ((b & 0xff )>>> 1) | 0x01); //注意必须加括号,因为 >>> 的优先级高于 &
4 运行后发现输出为我们想要的结果 0111 1101。运算步骤分解如下:
b 转化为int 1111 1000 转化为 11111111 11111111 11111111 11111000
和0xff 进行 & 操作 00000000 00000000 00000000 11111000
无符号右移1位 00000000 00000000 00000000 01111100
与 0x01 按位或 00000000 00000000 00000000 01111101
强制转化回byte 01111101
5 关于System.out.println();
byte b = (byte)0xf8;
System.out.println(b); --最终输出为-8
运算步骤为:
b 转化为int 1111 1000 转化为 11111111 11111111 11111111 11111000
取符号位 - -1111111 11111111 11111111 11111000
取返+1(因为是按补码运算) -0000000 00000000 00000000 00001000
输出 -8
最终结论:
1 区分使用 >> 和 >>>
2 在 >> 操作前要首先 & 0xff
3 注意符号优先级,正确使用括号。
4 需要强烈注意的一点是 & 的优先级小于 + . 因此 a = b & 0xff + 2000 的结果 可能不是你想要的
附:
打印byte,int 每个bit值的函数。
public static void printByte(byte b){
for(int i = 7; i >=0 ; i --){
int shiftleft = (b >> i) & 0x01;
System.out.print(shiftleft);
}
System.out.println();
}
public static void printInt(int b){
for(int i = 31; i >=0 ; i --){
int shiftleft = (b >> i) & 0x01;
System.out.print(shiftleft);
}
System.out.println();
}
1.1. java虚拟机整数
在java虚拟机中整数有byte、short、int、long四种 分别表示 8位、16位、32位、64位有符号整数。整数使用补码表示。
所以我们先了解一下原码和反码。
1.1.1. 原码
所谓原码就是符号位加上数字的二进制表示,int为例,第一位表示符号 (0正数 1负数)简单期间一个字节表示
+7的原码为: 00000111
-7的原码为: 10000111
对于原码来说,绝对值相等的正数和负数只有符号位不同。
1.1.2. 反码
一个数如果为正,则它的反码与原码相同;一个数如果为负,则符号位为1,(符号位不变化,其余位数取反)。
换言之 该数的绝对值取反(绝对值取反各位都取反)。
为了简单起见,我们用1个字节来表示一个整数:
+7的反码为:00000111
-7的反码为: 11111000
1.1.3. 补码
补码:一个数如果为正,则它的原码、反码、补码相同;一个数如果为负,去到反码然后加1。(反码加1就是补码)为了简单起见,我们用1个字节来表示一个整数:
+7的补码为: 00000111
-7的补码为: 11111001
1.1.4. 总结
正数:它的原码、反码、补码相同。
负数:反码符号位不变化,其余位数取反,补码符号位不变化其余各位原码取反(反码)+1换言之 反码+1
已知一个负数原码求反码:
步骤:
1.该数的绝对值取反
已知一个负数反码去求补码:
1.反码+1
已知一个负数求反码步骤:
正数的绝对值取反+1
已知一个负数求补码步骤:
1.二进制原码表示
2.符号位不变化为1 求反码。
3.符号位不变化,其余的加1.
实例如下:
-10求补码步骤:
-10的原码: 10000000 00000000 00000000 00001010
-10的反码: 11111111 11111111 11111111 11110101
-10的补码:11111111 11111111 11111111 11110110
已知一个负数的补码,将其转换为十进制数,步骤
1、先对各位取反;
2、将其转换为十进制数;
3、加上负号,再减去1。
例如:
11111010,最高位为1,所以是负数,先对各位取反得00000101,转换为十进制数得5,加上负号得-5,再减1得-6。
1.1.5. Java byte 类型的取值范围
1.确定byte是1个字节,也就是8位。
2.最大值 0111 1111
3.最小值 1000 0000。
4.0111 1111 就是127。
5.1000 0000 减去1是 1111 1111 按照位取反 1000 0000 得到-128
1.1.6. 常见问题
int a=232;
//0000 0000 1110 1000
System.out.println(Integer.toBinaryString(a));
System.out.println((byte) a);
输出结果为-24:
原理如下图: