Java 中字节移位的奇怪行为
Weird behaviour of bit-shifting with byte in Java
当我在 byte
上使用位移位时,我注意到在使用无符号右移 (>>>
) 时得到了奇怪的结果。对于 int
,右移(有符号:>>
和无符号:>>>
)都按预期运行:
int min1 = Integer.MIN_VALUE>>31; //min1 = -1
int min2 = Integer.MIN_VALUE>>>31; //min2 = 1
但是当我对 byte
做同样的事情时,无符号右移会发生奇怪的事情:
byte b1 = Byte.MIN_VALUE; //b1 = -128
b1 >>= 7; //b1 = -1
byte b2 = Byte.MIN_VALUE; //b2 = -128
b2 >>>= 7; //b2 = -1; NOT 1!
b2 >>>= 8; //b2 = -1; NOT 0!
我认为可能是编译器在内部将 byte
转换为 int
,但似乎不足以解释该行为。
为什么 Java 中的字节移位会这样?
byte
、short
和 char
的移位运算符始终在 int
上完成。
因此,真正被移动的值是 int
值 -128
,看起来像这样
int b = 0b11111111_11111111_11111111_10000000;
当您执行 b2 >>= 7;
时,您真正在做的是将上述值向右移动 7 位,然后仅考虑最后 8 位就转换回 byte
。
右移7位后得到
0b11111111_11111111_11111111_11111111;
当我们将其转换回字节时,我们只得到 11111111
,即 -1
,因为 byte
类型已签名。
如果你想得到答案 1 你可以移动 31 个位置而不用符号扩展。
byte b2 = Byte.MIN_VALUE; //b2 = -128
b2 >>>= 31;
System.out.println(b2); // 1
这正是因为 byte
是 promoted 到 int
在执行按位运算之前。 int -128
显示为:
11111111 11111111 11111111 10000000
因此,右移7位或8位仍然保留第7位1,所以结果是narrowed负值byte
。
比较:
System.out.println((byte) (b >>> 7)); // -1
System.out.println((byte) ((b & 0xFF) >>> 7)); // 1
通过b & 0xFF
,所有最高位都在移位之前被清除,因此结果如预期的那样产生。
Unary numeric promotion (§5.6.1) is performed on each operand separately.
并在 5.6.1 Unary Numeric Promotion 中:
if the operand is of compile-time type byte, short, or char, it is promoted to a value of type int by a widening primitive conversion
因此,您的 byte
操作数在移位之前被提升为 int
。值 -128
是 11111111111111111111111110000000
.
移位7、8次后,最低8位全为1,赋值给byte
时,会发生变窄原语转换。参考JLS 5.1.3 Narrowing Primitive Conversion :
A narrowing conversion of a signed integer to an integral type T simply discards all but the n lowest order bits, where n is the number of bits used to represent type T.
当我在 byte
上使用位移位时,我注意到在使用无符号右移 (>>>
) 时得到了奇怪的结果。对于 int
,右移(有符号:>>
和无符号:>>>
)都按预期运行:
int min1 = Integer.MIN_VALUE>>31; //min1 = -1
int min2 = Integer.MIN_VALUE>>>31; //min2 = 1
但是当我对 byte
做同样的事情时,无符号右移会发生奇怪的事情:
byte b1 = Byte.MIN_VALUE; //b1 = -128
b1 >>= 7; //b1 = -1
byte b2 = Byte.MIN_VALUE; //b2 = -128
b2 >>>= 7; //b2 = -1; NOT 1!
b2 >>>= 8; //b2 = -1; NOT 0!
我认为可能是编译器在内部将 byte
转换为 int
,但似乎不足以解释该行为。
为什么 Java 中的字节移位会这样?
byte
、short
和 char
的移位运算符始终在 int
上完成。
因此,真正被移动的值是 int
值 -128
,看起来像这样
int b = 0b11111111_11111111_11111111_10000000;
当您执行 b2 >>= 7;
时,您真正在做的是将上述值向右移动 7 位,然后仅考虑最后 8 位就转换回 byte
。
右移7位后得到
0b11111111_11111111_11111111_11111111;
当我们将其转换回字节时,我们只得到 11111111
,即 -1
,因为 byte
类型已签名。
如果你想得到答案 1 你可以移动 31 个位置而不用符号扩展。
byte b2 = Byte.MIN_VALUE; //b2 = -128
b2 >>>= 31;
System.out.println(b2); // 1
这正是因为 byte
是 promoted 到 int
在执行按位运算之前。 int -128
显示为:
11111111 11111111 11111111 10000000
因此,右移7位或8位仍然保留第7位1,所以结果是narrowed负值byte
。
比较:
System.out.println((byte) (b >>> 7)); // -1
System.out.println((byte) ((b & 0xFF) >>> 7)); // 1
通过b & 0xFF
,所有最高位都在移位之前被清除,因此结果如预期的那样产生。
Unary numeric promotion (§5.6.1) is performed on each operand separately.
并在 5.6.1 Unary Numeric Promotion 中:
if the operand is of compile-time type byte, short, or char, it is promoted to a value of type int by a widening primitive conversion
因此,您的 byte
操作数在移位之前被提升为 int
。值 -128
是 11111111111111111111111110000000
.
移位7、8次后,最低8位全为1,赋值给byte
时,会发生变窄原语转换。参考JLS 5.1.3 Narrowing Primitive Conversion :
A narrowing conversion of a signed integer to an integral type T simply discards all but the n lowest order bits, where n is the number of bits used to represent type T.