JAVA中有趣的位运算

2019-09-02 15:56:15 浏览数 (1)

&, |, ^, ~ 这些符号什么意思?有什么妙用?一起来感受它们的神奇吧~

当我们看一些源码的时候,经常会看到诸如 &、|、^、~ 的符号,这些就是位运算符。

位运算是直接对一个整形的二进制位进行操作,效率上比起加减乘除高不少,因此常运用在对性能很敏感的场景。

& 与运算

在二进制格式下,将两个数的每一位(1或0)分别做与运算(1&1=1,其它=0),得到一个新的二进制数。

代码语言:javascript复制
public class Bit {
    public static void main(String[] args) {
        /*
         *   十进制       二进制
         *     5        0 1 0 1    从最低位(右)开始比较,不足的为0
         *     与          与
         *    14        1 1 1 0
         *     =           =
         *     4        0 1 0 0
         */
        System.out.println(5 & 14);
    }
}
// 输出: 4

判断整数n是奇数还是偶数:

  • n & 1 = 0 偶数
  • n & 1 = 1 奇数

原理:二进制格式下,右边第一位是0则是偶数,反之为奇数,因此只需要和1进行与运算即可。

| 或运算

在二进制格式下,将两个数的每一位(1或0)分别做或运算(0|0=0,其它=1),得到一个新的二进制数。

代码语言:javascript复制
public class Bit {
    public static void main(String[] args) {
        /*
         *   十进制      二进制
         *     2        0 1 0     从最低位(右)开始比较,不足的为0
         *     或         或
         *     4        1 0 0
         *     =          =
         *     6        1 1 0
         */
        System.out.println(2 | 4);
    }
}
// 输出: 6

在Linux系统中,文件权限管理用1、2、4分别表示执行x、写w、读r的权限。

可以看做一个三位的二进制数,每一位分别表示一种权限的开启与否(1开启,0关闭),通过或运算组合就得到了不同的权限组合。

所以最高权限就是7,即二进制的“111”,拥有读、写、执行全部权限。而777权限则是所属用户、组用户、其他用户都拥有最高权限。

基于这个思路,我们只需要一个int或者long型的数字就可以存储几十个布尔类型的属性值,在某些场景下很有用。

^ 异或运算

代码语言:javascript复制
异或:相同为false,不同true

在二进制格式下,将两个数的每一位(1或0)分别做异或运算(0^0=0,1^1=0, 其它=1),得到一个新的二进制数。

代码语言:javascript复制
public class Bit {

    public static void main(String[] args) {
        /*
         *   十进制      二进制
         *     2        0 1 0     从最低位(右)开始比较,不足的为0
         *    异或       异或
         *     6        1 1 0
         *     =          =
         *     4        1 0 0
         */
        System.out.println(2 ^ 6);
    }
}
// 输出: 4

异或有个有趣的特性,它的逆运算是它本身,即A^B=C,C^B=A。基于这个特点,可以做一个简单的加密,把B作为秘钥,原文A用秘钥B加密后进行传输或存储等,使用时再用秘钥B进行解密。

通过异或操作还能实现两个数的交换,不需要中间值。(简单测了下性能并没有很棒棒)

代码语言:javascript复制
public class Bit {

    public static void main(String[] args) {
        int x = 2;// 010
        int y = 4;// 100
        x = x ^ y;// 110
        y = y ^ x;// 010
        x = x ^ y;// 100
        System.out.println("x = "   x);
        System.out.println("y = "   y);
    }
}

/* 输出:

x = 4
y = 2

*/

~ 非运算

在二进制格式下,将两个数的每一位(1或0)分别做非运算(~0=1,~1=0),得到一个新的二进制数。

代码语言:javascript复制
public class Bit {

    public static void main(String[] args) {
        System.out.println(~1);
    }

}
// 输出: -2

1进行非运算后值成了负数,不只是1,只要是正数,取非后都是负数,因为对于有符号的整数,最高位(最左边)是用来表示正负的,最高位为0是正数,1是负数。

正数1非运算后从“00000000000000000000000000000001”变成了“11111111111111111111111111111110”。

二进制表示负数的情况,要转成十进制需要两个步骤:

  1. 逐位取反 -> 00000000000000000000000000000001(2进制)
  2. 加1 -> 00000000000000000000000000000010(2进制) -> 2(10进制)
  3. 加上负号 -> -2(10进制)

总结

通过位运算可以巧妙且高效地达到某些目的,但如果不是很有必要,并不建议使用,毕竟可读性不高,别人看起来太痛苦(想想在阅读源码时看到一堆位运算的心情)。

这次简单介绍了与、或、非、异或,下次再讲讲移位操作的实践。

0 人点赞