java 中的位运算(“&”、“|”、“~”)详解

有没有小伙伴在看android源码或者一些大牛写的开源框架代码的时候,经常会看到代码中使用了很多位运算(“&”、“|”、“~”等)。看的时候一脸懵逼,也不知道为啥要这样子使用。想着反正不知道这些也不影响自己使用api接口或者开源框架,今天就来详细解读下这些位运算的使用。

按位与运算符(&)

参加运算的两个数据,按二进制位进行“与”运算。

运算规则:0&0=0;  0&1=0;   1&0=0;    1&1=1;

即:两位同时为“1”,结果才为“1”,否则为0

public class Test {
    public static void main(String[] args) {
        System.out.println("0 & 0 = " + (0 & 0));
        System.out.println("0 & 1 = " + (0 & 1));
        System.out.println("1 & 1 = " + (1 & 1));
    }
}

运算结果:
0 & 0 = 0
0 & 1 = 0
1 & 1 = 1

负数按补码形式参加按位与运算,这里什么是补码,我们留到后面再说。

按位或运算符(|)

参加运算的两个对象,按二进制位进行“或”运算。

运算规则:0|0=0;  0|1=1;  1|0=1;   1|1=1;

即 :参加运算的两个对象只要有一个为1,其值为1。

public class Test {
    public static void main(String[] args) {
        System.out.println("0 | 0 = " + (0 | 0));
        System.out.println("0 | 1 = " + (0 | 1));
        System.out.println("1 | 1 = " + (1 | 1));
    }
}

运算结果:
0 | 0 = 0
0 | 1 = 1
1 | 1 = 1

负数按补码形式参加按位或运算,补码同样留到后面再说。

取反运算符(~)

参加运算的一个数据,按二进制位进行“取反”运算。

运算规则:~1=0;  ~0=1;

public class Test {
    public static void main(String[] args) {
        System.out.println("~1 = " + (~1));
        System.out.println("~0 = " + (~0));
    }
}

运算结果:
~1 = -2
~0 = -1

纳尼,怎么和运算规则的结果不一样了。其实并不是楼主这里搞错了,有兴趣的同学可以自己用这个代码跑下,结果就是-2和-1;至于为什么会是这么多,接下来就要说道补码了。

补码

首先科普下:计算机会在内存中分配一块内存单元,大小为4个字节(Java中),用这4个字节来表示我们想表达的数字1。那么怎样用这四个字节来表达这个数字1呢?就是用补码的规则。

在看补码之前我们先看看原码 :原码就是将我们的数字转化为二进制后,用所给位数的最高位来代表正负,0代表正数,1代表负数。比如数字1,转换成二进制之后:0000 0001 ,用1000 0001表示-1,0000 0001就是1的原码。

我们再看补码,补码的计算规则是: 1、正数的补码等于其原码 
2、负数的补码是先求出负数的绝对值的原码(肯定是正数),然后把全部的位取反加1

比如数字1,转换成二进制之后:0000 0001,参考补码计算规则补码就是原码。比如数字-5,绝对值5,其原码是:0000 0101,所有位取反:1111 1010—>加1: 1111 1011 ,这样就得到了-5的补码。

知道了补码的概念了,我们回过头来瞧瞧,为什么会有(~1 = -2,~0 = -1)这两个结果出现。

首先是数字1,0000 0001取反之后通过取反位运算符结果是1111 1110,由于最高位是1为负数,所以从补码第二条规则得到结果是1000 0010,通过将二进制还原成十进制得到结果为-2,所以~1 = -2;(这一步不清楚的同学请参考补码第二条转换规则和十进制和二进制转换规则)。

同理数字0,转化成二进制0000 0000,取反1111 1111,最高位为1为负数,跟数字1运算规则一样的到结果为:1000 0001,十进制表示为-1。

按照这个规则我们看下~18,转化成二进制0001 0010,取反结果为1110 1101,最高位为负数,参考补码规则第二条转换后结果为:1001 0011,最高位为负数,转化成十进制结果为-19;

那么结果到底对不对了,是骡子是马,我们遛一遛就知道了,放到代码中跑如下代码:

public class Test {
    public static void main(String[] args) {
        System.out.println("~18 = " + (~18));
    }
}

运算结果:
~18 = -19

结果和我们算的一样,同理可以的到~-19结果为18。如果对结果有疑问的同学们可以自己用代码跑跑验证。

前面我们在说按位与运算和按位或运算的时候留了两个问题,现在来解答。

问题1:负数按补码形式参加按位与运算,这里什么是补码,我们留到后面再说。

举个栗子:我们要计算-5 & -3的结果。

首先根据我们已掌握的知识将-5转换成二进制1000 0101,补码为1111 1011,-3转换成二进制1000 0011,补码为1111 1101,补码通过按位与运算结果为1111 1001,通过补码第二条规则计算最终结果1000 0111,转换成十进制为-7;

问题2:负数按补码形式参加按位或运算,补码同样留到后面再说。

这里我就不作分析了,留给读者自己分析。

实际应用

有人说那你在这里逼逼了这么久,到底在实际中有啥应用了。不急,我们来看下android源码中的应用。先来看一段代码段

int specMode = MeasureSpec.getMode(measureSpec);
int specSize =  MeasureSpec.getSize(measureSpec);

估计熟悉自定义view的同学们一眼就知道这个是哪里的代码。在我们自定义view的时候,在测量布局时,经常要获取测量模式和测量的控件大小,然后实现我们自己的逻辑(这里不着重讲测量模式和测量大小),通过一个int型值就能获取specMode 和specSize ,刚开始觉得很不可思议,感觉有点类似对象的属性一样。那么他是怎么做到的了,我们查看下MeasureSpec源码:

...
public static class MeasureSpec {
private static final int MODE_SHIFT = 30; //移位位数为30
//int类型占32位,向左移位30位,该属性表示掩码值,用来与size和mode进行"&"运算,获取对应值。
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
    //向左移位30位,其值为00 + (高2位是00,低30位0)  , 即 0x0000(16进制表示)  
    public static final int UNSPECIFIED = 0 << MODE_SHIFT;  
    //向左移位30位,其值为01 + (高2位是01,低30位0)  , 即0x1000(16进制表示)  
    public static final int EXACTLY     = 1 << MODE_SHIFT;  
    //向左移位30位,其值为02 + (高2位是11,低30位0)  , 即0x2000(16进制表示)  
    public static final int AT_MOST     = 2 << MODE_SHIFT;  

    //创建一个整形值,其高两位代表mode类型,其余30位代表长或宽的实际值。可以是WRAP_CONTENT、MATCH_PARENT或具体大小exactly size  
    public static int makeMeasureSpec(int size, int mode) {  
        return size + mode;  
    }  
    //获取模式  ,与运算  
    public static int getMode(int measureSpec) {  
        return (measureSpec & MODE_MASK);  
    }  
    //获取长或宽的实际值 ,与运算  
    public static int getSize(int measureSpec) {  
        return (measureSpec & ~MODE_MASK);  
    }  

}  
...

首先是makeMeasureSpec会根据传入的size和mode组成一个32位int值,高2位代表mode类型,低30位代表size大小。

获取测量模式时:由于MODE_MASK左位移30位之后高2位是11,低30位是0,所以通过(measureSpec & MODE_MASK)运算就能获取measureSpec的高2位数据,即存储的mode值。

获取测量大小时,先对MODE_MASK取反,结果高2位变成0,低30位变成1,再和measureSpec进行按位与运算,最终得到低30位的数值,即测量的size大小。

到这里,这些个位运算大概有什么作用大家明白了么?

如果还是不太清楚,那么我们再通过一段代码来了解下:

public class Test {
    public static void main(String[] args) {
        int a = 0x001;
        int b = 0x002;
        int c = 0x005;
        int d = a | b;
        System.out.println("是否存在 a = " + ((d & a) == a));
        System.out.println("是否存在 b = " + ((d & b) == b));
        System.out.println("是否存在 c = " + ((d & c) == c));
    }
}

运算结果:
是否存在 a = true
是否存在 b = true
是否存在 c = false



public class Test {
    public static void main(String[] args) {
        int a = 0x001;
        int b = 0x002;
        int c = 0x005;
        int d = a | b;
        System.out.println("去掉 a 之前的 d " + d);
        d &= ~a;
        System.out.println("去掉 a 之后的 d " + d);
        d &= ~c;
        System.out.println("去掉 c 之后的 d " + d);
        d &= ~b;
        System.out.println("去掉 b 之后的 d " + d);
    }
}

运算结果:
去掉 a 之前的 d 3
去掉 a 之后的 d 2
去掉 c 之后的 d 2
去掉 b 之后的 d 0

这样的写法好处大家应该能体会的到,能节省空间,避免不必要的属性出现和维护成本,而且获取方便,编码简洁,位运算也更加高效。

尾巴

感谢你这么多金,这么帅还能看到最后。由于个人能里有限,本着将自己所理解的知识点分享给大家,避免可能会出现一些错误,欢迎拍砖!

发布了32 篇原创文章 · 获赞 59 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/abs625/article/details/84138486