Java 位运算笔记

一些零碎的知识点总是似懂非懂,用法老是模棱两可,每次都要去网络上查询,长时间不用又忘记了。比如 Java 中的位运算。今天抽空归纳总结一下,加强一下记忆。

一、原码、反码和补码

1.1 原码

一个数在计算机中的二进制表示形式,叫做这个数的机器数。机器数是带符号的,在计算机用一个数的最高位存放符号,正数为 0, 负数为 1。所以,为区别起见,将带符号位的机器数对应的真正数值称为机器数的真值。

原码就是符号位加上真值的绝对值,即用第一位表示符号,其余位表示值。比如 8 位二进制:

[+1] 原 = 0000 0001
[-1] 原 = 1000 0001

第一位是符号位,因为第一位是符号位,所以 8 位二进制数的取值范围就是:[1111 1111 , 0111 1111],即:[-127 , 127]

1.2 反码

反码的表示方法是:正数的反码是其本身,负数的反码是在其原码的基础上,符号位不变,其余各个位取反。

[+1] = [00000001] 原 = [00000001] 反
[-1] = [10000001] 原 = [11111110] 反

1.3 补码

补码的表示方法是:正数的补码就是其本身,负数的补码是在其原码的基础上,符号位不变,其余各位取反,最后 + 1。(即在反码的基础上 + 1)

[+1] = [00000001] 原 = [00000001] 反 = [00000001] 补
[-1] = [10000001] 原 = [11111110] 反 = [11111111] 补

二、左移运算(«)

value << num

num 指定要移位值;value 移动的位数。

将左操作数(value)转为二进制数后向左边移动 num 位,并且在低位补 0,高位丢弃。

例如:5 << 2

0000 0000 0000 0000 0000 0000 0000 0101     5 的补码(同原码)
0000 0000 0000 0000 0000 0000 0001 0100     左移 2 位后,低位补 0。换算成 10 进制为 20

如果移动的位数超过了该类型的最大位数,那么编译器会对移动的位数取模。如:对 int 类型(最大位数 32)的数值移动 33 位,实际上只移动了 33 % 32 = 1 位。

注:n 位二进制,最高位为符号位,因此表示的数值范围:$ -2^{(n-1)} $ —— $ 2^{(n-1)}-1 $,所以模为:$ 2^{(n-1)} $。

在数字没有溢出的前提下,对于正数和负数,左移一位都相当于乘以 2 的 1 次方,左移 n 位就相当于乘以 2 的 n 次方。如:5 << 2 相当于 $ 5 * 2^2 = 20 $。

如果移进高阶位(int 31 或 long 63 位),那么该值将变为负值。如:1 << 31 = -2147483648

三、右移运算(»)

value >> num

num 指定要移位值;value 移动的位数。

将左操作数(value)转为二进制数后向右边移动 num 位,符号位不变,高位补上符号位(若左操作数是正数,则高位补 0,若左操作数是负数,则高位补 1),低位丢弃。

右移时,被移走的最高位(最左边的位)由原来最高位的数字补充,这叫做符号位扩展(保留符号位)(sign extension),在进行右移操作时用来保持负数的符号。

例如:7 >> 2

0000 0000 0000 0000 0000 0000 0000 0111     7 的补码(同原码)
0000 0000 0000 0000 0000 0000 0000 0001     右移 2 位后,高位补 0。换算成 10 进制为 1

例如:-7 >> 2

1000 0000 0000 0000 0000 0000 0000 0111     -7 的原码
1111 1111 1111 1111 1111 1111 1111 1000     -7 的反码
1111 1111 1111 1111 1111 1111 1111 1001     -7 的补码
1111 1111 1111 1111 1111 1111 1111 1110     右移 2 位后,高位补 1
1000 0000 0000 0000 0000 0000 0000 0010     补码转原码。换算成 10 进制为 -2

正数右移 n 位相当于除以 2 的 n 次方并且舍弃了余数。如:7 >> 2 相当于: $ 7 / 2^2 = 1 $。

负数右移 n 位相当于除以 2 的 n 次方,如果有余数 -1。如:-7 >> 2 相当于: $ 7 * 2^2 -1= -2 $。

四、无符号右移(»>)

value >>> num

num 指定要移位值;value 移动的位数。

将左操作数(value)转为二进制数后向右边移动 num 位,0 补最高位(忽略了符号位扩展)。

无符号右移运算只是对 32 位和 64 位的值有意义。

例如:-7 >>> 2

1000 0000 0000 0000 0000 0000 0000 0111     -7 的原码
1111 1111 1111 1111 1111 1111 1111 1001     -7 的补码
0011 1111 1111 1111 1111 1111 1111 1110     右移 2 位后,高位补 0。换算成 10 进制为 1073741822

五、位逻辑运算符

5.1 与运算(&

与运算:两个运算数比较位都是 1,则结果为 1,否则为 0。例如:5 & 3 = 1

0000 0000 0000 0000 0000 0000 0000 0101     5 转换为二进制
0000 0000 0000 0000 0000 0000 0000 0011     3 转换为二进制
0000 0000 0000 0000 0000 0000 0000 0001     换算成 10 进制为 1

5.2 或运算(|

或运算:两个运算数比较位有一个为 1,则结果为 1,否则为 0。例如:5 | 3 = 7

0000 0000 0000 0000 0000 0000 0000 0101     5 转换为二进制
0000 0000 0000 0000 0000 0000 0000 0011     3 转换为二进制
0000 0000 0000 0000 0000 0000 0000 0111     换算成 10 进制为 7

5.3 异或运算(^

异或运算:两个运算数比较位不同时,其结果是 1,否则为 0。例如:5 ^ 3 = 6

0000 0000 0000 0000 0000 0000 0000 0101     5 转换为二进制
0000 0000 0000 0000 0000 0000 0000 0011     3 转换为二进制
0000 0000 0000 0000 0000 0000 0000 0110     换算成 10 进制为 6

5.4 非运算(~

非运算:也叫做补,一元运算符,对其运算数的每一位取反。例如:~5 = -6

0000 0000 0000 0000 0000 0000 0000 0101     5 转换为二进制
1111 1111 1111 1111 1111 1111 1111 1010     取非后的原码
1000 0000 0000 0000 0000 0000 0000 0110     转换补码,换算成 10 进制为 -6

六、其它

  • Java 中整数类型(byte、short、int 和 long)在内存中是以有符号的二进制补码表示。所以位运算时,首先要转换为原码。

  • 补码转原码:补码转原码和原码转补码的方法是一样的,取反 + 1(补码的补码是原码)。

  • 当位运算数是 byte 和 short 类型时,将自动把这些类型扩大为 int 型(32 位)。

  • 计算出 n 位二进制数所能表示的最大十进制数位移算法:-1L ^ (-1L << n)~(-1L << n)

  • byte 和 int 相互转换

int i = 234;

byte b = (byte) i; // 结果:b = -22
// 转换过程:
// 0000 0000 0000 0000 0000 0000 1110 1010      # int 234 的补码(与原码相等)
//                               1110 1010      # byte 低位截取
//                               1001 0110      # 求得补码,转为 10 进制为 -22

int x = b ; // 结果为:x = -22;8 位 byte 的转 32 的 int,值不变。
int y = b & 0xff; // 结果为:x = 234; 可以通过将其和 0xff 进行位与(&)得到它的无符值
// 转换过程:
// 1001 0110                                    # byte -22 的原码
// 1000 0000 0000 0000 0000 0000 0001 0110      # int -22 的原码
// 1111 1111 1111 1111 1111 1111 1110 1010      # int -22 补码
// 0000 0000 0000 0000 0000 0000 1111 1111      # 0xff 的二进制数
// 0000 0000 0000 0000 0000 0000 1110 1010      # 和 0xff 进与操作的结果,转换为 10 进制为 234