Java 位运算

编程 / Java / 2022-08-13

概述

从现代计算机中所有的数据二进制的形式存储在设备中。即 0、1 两种状态,计算机对二进制数据进行的运算都是叫位运算,即将符号位共同参与运算的运算。

Java 中有以下的运算符:

运算符 名称 示例 结果 说明
<< 左移 4<<2 16 符号左边的操作数左移指定的位数
>> 右移 4>>1 2 将符号的左边的操作数右移指定位数
>>> 无符号右移 4>>>1 0 将符号左边的操作数右移指定的位数
& 与运算 4&2 0 两个二进制位,只要有一个为0,那么结果就为0,否则结果为1
| 或运算 4!2 6 两个二进制位,只要有一个为1,那么结果就为1,否则结果为0
^ 异或运算 4^2 6 相同二进制位,结果为0;不同的二进制位,结果为1
~ 取反 -4 -5 二进制位,0变1;1变0

与、或、异或、取反

class Playground {

    public static void main(String[ ] args) {

        int a = 3, b = 4;

        // 与运算
        System.out.println(a & b);

        // 或运算
        System.out.println(a | b);

        // 异或运算
        System.out.println(a ^ b);

        // 取反
        System.out.println(~a);

    }

}

a 和 b 变量都是正数,在Java中,int 是4字节32位带符号的二进制补码整数。

a 和 b,十进制为 3 和 4 ;正数的二进制最高位为 0,所以二进制为:0000 0000 0000 0000 0000 0000 0000 00110000 0000 0000 0000 0000 0000 0000 0100

在上述程序中的运算,使用二进制表示如下:

与运算

两个二进制位,只要有一个为0,那么结果就为0,否则结果为1

a & b

变量 二进制 十进制
a 0000 0000 0000 0000 0000 0000 0000 0011 3
b 0000 0000 0000 0000 0000 0000 0000 0100 4
a & b 0000 0000 0000 0000 0000 0000 0000 0000 0

或运算

a | b

两个二进制位,只要有一个为1,那么结果就为1,否则结果为0

变量 二进制 十进制
a 0000 0000 0000 0000 0000 0000 0000 0011 3
b 0000 0000 0000 0000 0000 0000 0000 0100 4
a | b 0000 0000 0000 0000 0000 0000 0000 0111 7

异或运算

a ^ b

相同二进制位,结果为0;不同的二进制位,结果为1

变量 二进制 十进制
a 0000 0000 0000 0000 0000 0000 0000 0011 3
b 0000 0000 0000 0000 0000 0000 0000 0100 4
a ^ b 0000 0000 0000 0000 0000 0000 0000 0111 7

取反运算

~a

0变1;1变0。

需要注意,计算机进行二进制运算,都是使用原码的补码进行运算,上述其他运算的二进制的最高为0,说明是正数,正数的补码就是原码,所以直接使用补码转十进制。

但是此例中,取反之后,最高位为1,说明此时为负数,而负数是使用补码来进行运算,所以需要将此负数的补码,转为原码,再转为十进制。

负数:原码 转 补码:原码取反; + 1;
负数:补码 转 原码:补码取反;+ 1;(补码的补码就是原码)

变量 二进制 十进制 说明
a 0000 0000 0000 0000 0000 0000 0000 0011 3
取反 1111 1111 1111 1111 1111 1111 1111 1100 取反之后的二进制最高位为1,说明此时为负数,需要将此二进制位补码,需要转为原码
计算原码 1000 0000 0000 0000 0000 0000 0000 0011 补码取反,符号位不变
计算原码 1000 0000 0000 0000 0000 0000 0000 0100 -4 +1

需要注意,Integer.toBinaryString(),获取的是当前参数的补码字符串,而非原码!!!
源代码注释:The unsigned integer value is the argument plus 2^32 if the argument is negative;如果参数负数,那么返回值为 负数 + 2^32,结果就是负数的补码!!!

左移和右移

左移

左移说明:符号左边的操作数左移指定的位数。

左移语法:

操作数 << 位数
  1. 首先将左边的操作数转为二进制
  2. 然后按照要求左移指定位数,左边最高位丢弃,右边补0

3<<2

3的二进制为:

0000 0000 0000 0000 0000 0000 0000 0011

左移两位,左边最高位丢弃(红色的部分丢弃),右边补0(蓝色部分补0):

0000 0000 0000 0000 0000 0000 0000 1100


右移

将符号的左边的操作数右移指定位数。

右移语法:

操作数>>位数
  1. 首先将左边的操作数转为二进制
  2. 然后按要求右移指定位数:最高位是0,左边补齐0;最高位是1,左边补齐1

24>>2

24的二进制:

0000 0000 0000 0000 0000 0000 0001 1000

右移2位(红色部分丢弃),最高位是0,左边补齐0(蓝色部分);最高位是1,左边补齐1

0000 0000 0000 0000 0000 0000 0000 0110

无符号右移

将符号左边的操作数右移指定的位数

语法:

操作数>>>位数
  1. 首先将符号左边的操作数转为二进制
  2. 然后按照要求右移指定位数,左边始终补齐0

规律总结

  • 左移 n 位 = 左移操作数 x 2 的 n 次方
  • 正数右移 n 位 = 右移操作数 / 2 的 n 次方
  • 负数右移/左移:先算出操作数补码,让补码右移/左移,在将补码转成原码

注意

以上运算操作,如果出现负数,需用负数的补码进行运算操作,运算结果为补码,需要再将补码转为原码才是最终结果。过程参考[[#取反运算]]

应用场景

奇偶判断(与运算)

思路:

假设二进制最低位是 n , 那么十进制换算为: n x 2^0 = 1 / 0。

二进制其他位的结果均为 2 的次方,是偶数 x,那么 x 加上最低位的结果(1 / 0)就可以判断出此数为奇偶了。偶数 + 1 = 奇数;偶数 + 偶数 = 奇数。

十进制数 1 的二进制为 0001,那么可通过 1 进行与运算判断。

例如数字 3 & 1:3 的二进制为 0011,1 的二进制为 0001。这两个二进制进行与运算,结果为二进制 0001,换成十进制也是 1,就说明此数为负数了。

3的二进制 0 0 1 1
1的二进制 0 0 0 1
与运算结果 0 0 0 1
/**  
 * 判断传入的数字是否为偶数,通过与运算进行判断。  
 * @param number 数字  
 */  
public static void isEvenNumber(int number) {  
    if ((number & 1) == 0) {  
        System.out.println("偶数:" + number);  
    } else {  
        System.out.printf("奇数:" + number);  
    }  
}

两数交换(异或运算)

思路:一个数对自己异或,再对另一个数异或,结果为另一个数。

说明:一个数对自己异或运算,结果为 0,0 再对另一个数异或,结果就为另一个数。

/**  
 * 将传输的两个参数值进行交换  
 *  
 * @param arg1 参数1  
 * @param arg2 参数2  
 */public static void swap(int arg1, int arg2) {  
    System.out.println(arg1 ^ arg1 ^ arg2); 
    System.out.println(arg2 ^ arg2 ^ arg1);  
}

进阶写法

/**  
 * 将传输的两个参数值进行交换  
 *  
 * @param arg1 参数1  
 * @param arg2 参数2  
 */public static void swap(int arg1, int arg2) {  
    System.out.println("原 arg1:" + arg1);  
    System.out.println("原 arg2:" + arg2);  
  
    arg1 =  arg1 ^ arg2;  
  
    // 此时 arg1 = arg1 ^ arg2, 所以 arg2 = arg1 ^ arg2 ^ arg2。此时 arg2 交换完成,变成了 arg1 的值  
    arg2 = arg1 ^ arg2;  
  
    // 此时 arg1 = arg1 ^ arg2,而 arg2 已经变成了 arg1 的值,所以:arg1 ^ arg2 ^ arg1。此时 arg1 交换完成,变成了 arg2 的值  
    arg1 = arg1 ^ arg2;  
  
    System.out.println("新 arg1:" + arg1);  
    System.out.println("新 arg2:" + arg2);  
}