Android上关于位运算和多种状态存储方法

172 阅读10分钟

有时候,当我们需要记录下多种状态,并且每一种都是可以同时存在的,

他们是并列关系,每种状态可以有两个结果,是否(true|false)。在很多框架中,会使用到下面这种方法。

下面先来看一段代码,这段代码来自Glide源代码

BaseRequestOptions

private static final int UNSET = -1;
private static final int SIZE_MULTIPLIER = 1 << 1;
private static final int DISK_CACHE_STRATEGY = 1 << 2;
private static final int PRIORITY = 1 << 3;
private static final int ERROR_PLACEHOLDER = 1 << 4;
private static final int ERROR_ID = 1 << 5;
private static final int PLACEHOLDER = 1 << 6;
private static final int PLACEHOLDER_ID = 1 << 7;
private static final int IS_CACHEABLE = 1 << 8;
private static final int OVERRIDE = 1 << 9;
private static final int SIGNATURE = 1 << 10;
private static final int TRANSFORMATION = 1 << 11;
private static final int RESOURCE_CLASS = 1 << 12;
private static final int FALLBACK = 1 << 13;
private static final int FALLBACK_ID = 1 << 14;
private static final int THEME = 1 << 15;
private static final int TRANSFORMATION_ALLOWED = 1 << 16;
private static final int TRANSFORMATION_REQUIRED = 1 << 17;
private static final int USE_UNLIMITED_SOURCE_GENERATORS_POOL = 1 << 18;
private static final int ONLY_RETRIEVE_FROM_CACHE = 1 << 19;
private static final int USE_ANIMATION_POOL = 1 << 20;

1 << 1 是一个位运算表达式,其中 << 是左移操作符。这个表达式的意思是将数字 1 在二进制表示中的位向左移动 1 位。以此类推,到 1 << 20 最后一个向左移动20位。每一个状态都是用一个整数存储。当我们转换成整数表示之后。

private static final int UNSET = -1; // 在Java中,-1的二进制表示是所有位都为1(32位),但在10进制中它就是-1  
private static final int SIZE_MULTIPLIER =         2; // 1 << 1  
private static final int DISK_CACHE_STRATEGY =     4; // 1 << 2  
private static final int PRIORITY =                8; // 1 << 3  
private static final int ERROR_PLACEHOLDER =       16; // 1 << 4  
private static final int ERROR_ID =                32; // 1 << 5  
private static final int PLACEHOLDER =             64; // 1 << 6  
private static final int PLACEHOLDER_ID =          128; // 1 << 7  
private static final int IS_CACHEABLE =            256; // 1 << 8  
private static final int OVERRIDE =                512; // 1 << 9  
private static final int SIGNATURE =               1024; // 1 << 10  
private static final int TRANSFORMATION =          2048; // 1 << 11  
private static final int RESOURCE_CLASS =          4096; // 1 << 12  
private static final int FALLBACK =                8192; // 1 << 13  
private static final int FALLBACK_ID =             16384; // 1 << 14  
private static final int THEME =                   32768; // 1 << 15  
private static final int TRANSFORMATION_ALLOWED =  65536; // 1 << 16  
private static final int TRANSFORMATION_REQUIRED = 131072; // 1 << 17  
private static final int USE_UNLIMITED_SOURCE_GENERATORS_POOL = 262144; // 1 << 18  
private static final int ONLY_RETRIEVE_FROM_CACHE = 524288; // 1 << 19  
private static final int USE_ANIMATION_POOL =      1048576; // 1 << 20
  • 二进制表示

简单来说,左移 n 位相当于乘以 2n 次方。所以 1 << 1 相当于 1 * 2^1 = 2,当我们继续把它转成二进制

rivate static final int UNSET = -1; // 在32位整数中,这是 11111111 11111111 11111111 11111111(二进制)  
private static final int SIZE_MULTIPLIER =         0000 0000 0000 0000 0000 0000 0000 0010; // 1 << 1  
private static final int DISK_CACHE_STRATEGY =     0000 0000 0000 0000 0000 0000 0000 0100; // 1 << 2  
private static final int PRIORITY =                0000 0000 0000 0000 0000 0000 0000 1000; // 1 << 3  
private static final int ERROR_PLACEHOLDER =       0000 0000 0000 0000 0000 0000 0001 0000; // 1 << 4  
private static final int ERROR_ID =                0000 0000 0000 0000 0000 0000 0010 0000; // 1 << 5  
private static final int PLACEHOLDER =             0000 0000 0000 0000 0000 0000 0100 0000; // 1 << 6  
private static final int PLACEHOLDER_ID =          0000 0000 0000 0000 0000 0000 1000 0000; // 1 << 7  
private static final int IS_CACHEABLE =            0000 0000 0000 0000 0000 0001 0000 0000; // 1 << 8  
private static final int OVERRIDE =                0000 0000 0000 0000 0000 0010 0000 0000; // 1 << 9  
private static final int SIGNATURE =               0000 0000 0000 0000 0000 0100 0000 0000; // 1 << 10  
private static final int TRANSFORMATION =          0000 0000 0000 0000 0000 1000 0000 0000; // 1 << 11  
private static final int RESOURCE_CLASS =          0000 0000 0000 0000 0001 0000 0000 0000; // 1 << 12  
private static final int FALLBACK =                0000 0000 0000 0000 0010 0000 0000 0000; // 1 << 13  
private static final int FALLBACK_ID =             0000 0000 0000 0000 0100 0000 0000 0000; // 1 << 14  
private static final int THEME =                   0000 0000 0000 0000 1000 0000 0000 0000; // 1 << 15
private static final int TRANSFORMATION_ALLOWED  = 0000 0000 0000 0000 0001 0000 0000 0000;  // 1 << 16  
private static final int TRANSFORMATION_REQUIRED = 0000 0000 0000 0010 0000 0000 0000 0000; // 1 << 17  
private static final int USE_UNLIMITED_SOURCE_GEN= 0000 0000 0000 0100 0000 0000 0000 0000;  // 1 << 18  
private static final int ONLY_RETRIEVE_FROM_CACHE =0000 0000 0000 1000 0000 0000 0000 0000;  // 1 << 19  
private static final int USE_ANIMATION_POOL =      0000 0000 0001 0000 0000 0000 0000 0000;  // 1 << 20

当一个int,用二进制去表示的时候,它可以有32位。所以每一位都可以用来存在一种状态。当把每种状态都存满之后,就是以上状态合并。0000 0000 0001 1111 1111 1111 1111 1110

  • 存储数据

那平时怎么去存储,或者清除掉呢,比如我需要把SIZE_MULTIPLIER记录下来,或者把SIZE_MULTIPLIER清除掉,记录下来就是把对应位置设置成1,删除掉就是把对应位置还原回到0

fields = fields | SIZE_MULTIPLIER; 等同 fields |= SIZE_MULTIPLIER;

通过|运算符就是存储,简单理解就是当两个数组合的时候,如果有一个是1,那对应位置就是1.

假如当前fields值是

0000 0000 0001 1111 1111 1111 1111 1110 (fields)| 0000 0000 0000 0000 0000 0000 0000 0010 (SIZE_MULTIPLIER)

他们最后结果就是0000 0000 0001 1111 1111 1111 1111 1110 也就是被记录下来了,这种算法,它只关注对应位置,其他位置并不影响,这里对应位置应该就是第二位了。

  • 移除数据

fields = fields & ~PRIORITY; 等同 fields &= ~PRIORITY;

假如原来的值是fields = 0000 0000 0001 1111 1111 1111 1111 1110

某个状态 PRIORITY = 0000 0000 0000 0000 0000 0000 0000 1000

取反~PRIORITY = 1111 1111 1111 1111 1111 1111 1111 0111

fields = fields & ~PRIORITY

0000 0000 0001 1111 1111 1111 1111 1110 (fields) & 1111 1111 1111 1111 1111 1111 1111 0111 (~PRIORITY)
=
0000 0000 0001 1111 1111 1111 1111 0110

&运算规则是两个都是1的时候,才是1,所以上面计算结果为

0000 0000 0001 1111 1111 1111 1111 0110 其实变化就是第四位变成0

  • 提取数据 先来看一下关于View可见性的状态
public static final int VISIBLE = 0x00000000;
public static final int INVISIBLE = 0x00000004;
public static final int GONE = 0x00000008;

对应二进制,省略掉前面是0的位

public static final int VISIBLE = 0000;
public static final int INVISIBLE = 0100;
public static final int GONE = 1000;

观察数据可以发现,它是存储在mViewFlags后面位,那么怎么把View状态提取出来?

public int getVisibility() {
    return mViewFlags & VISIBILITY_MASK;
}

通过VISIBILITY_MASK & mViewFlags

static final int VISIBILITY_MASK = 0x0000000C;
对应二进制,省略掉前面是0的位
static final int VISIBILITY_MASK = 1100

也就是取一个对应位置都是1MASK&运算就可以提取状态了。

  • 组合两个数,形成一个唯一的Key

把两个参数结合一起,作为判断条件,比如属性A,属性B,两个条件一起满足的时候,才执行一些逻辑。在一些缓存场景中,需要用到。

下面看一个二进制

00000000000000000000000000001111 00000000000000000000000000001111

这个二进制有64位,前面32位假如存储A,后面32位存储B,通过这样的组合生成一个key,总共就是64位,在Java中可以用一个long表示。下面继续看一下实际的源代码使用。

final boolean isColorDrawable;
final DrawableCache caches;
final long key;
if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
&& value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
    isColorDrawable = true;
    caches = mColorDrawableCache;
    key = value.data;
} else {
    isColorDrawable = false;
    caches = mDrawableCache;
    key = (((long) value.assetCookie) << 32) | value.data;
}

key = (((long) value.assetCookie) << 32) | value.data

这段代码就是把assetCookie存储在一个long的前面32位,把data存储在后面32位。比如

假如assetCookie=9,那它的二进制就是1001,转成long之后,64位就是前面一堆

0000000000000000000000000000000000000000000000000000000000001001

然后左移动32位

0000000000000000000000000000100100000000000000000000000000000000

假如data=2264,那它的二进制就是100011011000

当把assetCookie << 32|data,也就是0000000000000000000000000000100100000000000000000000000000000000assetCookie << 32

0000000000000000000000000000000000000000000000000000100011011000data

两个重叠之后

0000000000000000000000000000100100000000000000000000100011011000

  • 总结

三种运算符使用 & | ~

如何通过一个Int整数表示32种状态