Java 位运算

技术 · 2022-08-13

概述

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

Java 中有以下的运算符:

运算符名称示例结果说明
<<左移4<<216符号左边的操作数左移指定的位数
>>右移4>>12将符号的左边的操作数右移指定位数
>>>无符号右移4>>>10将符号左边的操作数右移指定的位数
&与运算4&20两个二进制位,只要有一个为0,那么结果就为0,否则结果为1
|或运算4!26两个二进制位,只要有一个为1,那么结果就为1,否则结果为0
^异或运算4^26相同二进制位,结果为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 0011 和 0000 0000 0000 0000 0000 0000 0000 0100

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

与运算

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

a & b

变量二进制十进制
a0000 0000 0000 0000 0000 0000 0000 00113
b0000 0000 0000 0000 0000 0000 0000 01004
a & b0000 0000 0000 0000 0000 0000 0000 00000

或运算

a | b

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

变量二进制十进制
a0000 0000 0000 0000 0000 0000 0000 00113
b0000 0000 0000 0000 0000 0000 0000 01004
a | b0000 0000 0000 0000 0000 0000 0000 01117

异或运算

a ^ b

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

变量二进制十进制
a0000 0000 0000 0000 0000 0000 0000 00113
b0000 0000 0000 0000 0000 0000 0000 01004
a ^ b0000 0000 0000 0000 0000 0000 0000 01117

取反运算

~a

0变1;1变0。

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

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

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

变量二进制十进制说明
a0000 0000 0000 0000 0000 0000 0000 00113
取反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的二进制0011
1的二进制0001
与运算结果0001

/**  
 * 判断传入的数字是否为偶数,通过与运算进行判断。  
 * @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);  
}

Java 编程
Theme Jasmine by Kent Liao