精妙的位运算

位运算

  • 与运算(and)

    &表示,当两个相同位对应的数都是 1 的时候,该位获得的结果才是 1,否则为 0
    例如说6 & 11,转换为二进制就是0110 & 1011,结果为0010
    在位运算中常常与mask进行提取对应位值的操作。

  • 或运算(or)

    |表示,当两个相同位对应的数只要有一个数 1 的时候,该位获得的结果为 1,否则为 0
    还是用 6 和 11 这两个数做例子,就是0110 | 1011 = 1111在位运算中可以起到赋值的作用。

  • 非运算(not)

    ~表示将每一位按位取反(原来是 0 结果就是 1,原来是 1 结果就是 0)。~110 = 001
    在对Int值进行非运算时,因为计算机中存储的是补码,此时的非运算对补码进行取反后,再还原为原码后就会变为奇怪的值。
    为何~1等于-2,~0等于-1呢

  • 异或运算(xor)

    ^表示,当两个相同位对应的数字不同的时候为 1,否则为 0,可以用A ^ B == 0来判断两个数是否相等。例如说:0110 ^ 1011 = 1101可以用于判断两值是否相同并且提取出改变的位

  • 左移(shl)

    <<表示,a << b表示 a 左移 b 位,由于移位在末位多出来的未知数字补零。
    在这里面可以等价为a * 2^b这个运算(针对十进制)。0001 << 1 = 0010 相当于1 * 2^1 = 2

  • 右移(shr)

    >>表示,a >> b表示将 a 右移 b 位,原本的末位进行右移后会被舍弃,左边的用原有标志位补充,正数补0,负数补1。同样的,右移在十进制里面也可以近似为a / (2^b)的形式,不过要对结果取整,也不一定准确,只能够说意思大概如此。

  • 无符号右移

    >>>表示,不管正负标志位为 0 还是 1,将该数的二进制码整体右移,左边部分总是以 0 填充,右边部分舍弃。

运算优先级: ~ > << = >>= >>> > & > ^ > | > && > ||

Android源码中的应用

View

View.java的源码中,各种View状态都是保存到类型为IntmViewFlags字段中,每一位或多位代表一个属性的状态,例如static final int ENABLED_MASK = 0x00000020;代表了从二进制的右侧数第6位的值代表了当前是否为ENABLED状态。

mViewFlags共使用了32位来存储状态,源码中表示为0x00000000,每一个0都是4位。而0x00000010中1为使用该4位中的最后一位,相同的,2为第三位,4为第二位,8为第一位。

修改mViewFlags的值在setFlags(flag,mask)方法中:

View.java
1
2
3
4
5
6
7
8
9
10
11
12
void setFlags(int flags, int mask) {
// ...
int old = mViewFlags;
// 设置新的flag值
mViewFlags = (mViewFlags & ~mask) | (flags & mask);
// 判断是否值没有改变
int changed = mViewFlags ^ old;
if (changed == 0) {
return;
}
// ...
}

其中,(mViewFlags & ~mask)是将mask所标志的所在位原有值去除,(flags & mask)提取出要赋值的新值,并通过或运算|赋值至mViewFlags。此时新旧两值通过异或^判断值是否发生了改变。如果不相等,当前changed中为1的位为本次修改的位。

举个例子:假设当前mViewFlags = 0x00400010,此时,我们想要将当前View设置为disable状态,调用了setFlags(DISABLED,ENABLED_MASK)int DISABLED = 0x00000020,int ENABLED_MASK = 0x00000020,那么,当前的(mViewFlags & ~mask) = 0x00400010 & ~0x00000020 = 0x00400010 & 0xffffffdf = 0x00400010,而(flags & mask) = 0x00000020 & 0x00000020 = 0x00000020,最终mViewFlags = 0x00400010 | 0x00000020 = 0x00400030,最后changed计算出mViewFlags ^ old0x00000020,也就是等于mask的值。

setFlags的剩余代码中,可以看到其他的位运算

View.java setFlags()
1
2
3
4
5
6
7
8
if (((changed & FOCUSABLE) != 0){ // 等于0则说明对应位没有发生变化,不等于0则说明发生了变化
//...
}
if (((old & FOCUSABLE) == FOCUSABLE) {// FOCUSABLE此时相当于一个mask,取出对应位进行值比较
//...
}
mPrivateFlags |= PFLAG_DRAWN; // 赋值新的属性值

位掩码BitMask的作用

mask标记了某一组属性所使用的一个或多个位,在获取对应属性状态时可以对mask进行&运算获得对应的值,屏蔽其他位的影响。保证了一定的安全性、可靠性。

MeasureSpec

MeasureSpec是一个32位的int值,高2位为SpecMode,低30位为SpecSize

View.MeasureSpec.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
// SpecMode的位掩码 0x30000000
private static final int MODE_MASK = 0x3 << MODE_SHIFT;

public static final int UNSPECIFIED = 0 << MODE_SHIFT;

public static final int EXACTLY = 1 << MODE_SHIFT;

public static final int AT_MOST = 2 << MODE_SHIFT;

public static int getMode(int measureSpec) {
// 与位掩码and运算得出SpecMode
return (measureSpec & MODE_MASK);
}

public static int getSize(int measureSpec) {
// 去除SpecMode所在位,剩下位即是Size
return (measureSpec & ~MODE_MASK);
}

public static int makeMeasureSpec(int size,int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
// 与View的flag赋值一样,先清除之前设置的值,后进行and运算赋值
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
}

位运算的优缺点

优点

  • 省去了创建过多的属性变量,将多个属性集中到一个Int中进行管理
  • 运算效率高,并且可以同时进行多个属性的赋值、修改操作。例如setFlags(A|B,A_MASK|B_MASK)

缺点

不经常使用的话看起来太费劲,并不直观。

参考文献

弄懂 Android 源码中那些巧妙位运算

谈谈位运算和在Android中的运用

作者

Hanani

发布于

2022-06-22

更新于

2022-06-22

许可协议