前言
经常看源码上有些例如 一个int数字的不同位拿来存储多个值,一个int数字表示多个状态之类的。
这篇文章来分析下常用的一些位运算。
一些前置概念
左移,右移,原码,反码,补码这些基础运算就不解释了,网上一大堆。
这一篇还比较详细
https://www.cnblogs.com/zhangziqiu/archive/2011/03/30/computercode.html
操作符 | 描述 | 例子 |
---|---|---|
按位与& |
如果相对应位都是1,则结果为1,否则为0 | 1&3=1 ,即0001&0011 =0001 |
按位或| |
如果相对应位都是0,则结果为0,否则为1 | 1|3=3 ,即0001|0011 =0011 |
按位异或^ |
相对应位一个0,一个1,则结果1,否则0 | 1^3=2 ,即0001^0011=0010 |
按位取反~ |
按位取反运算符翻转操作数的每一位,即0变成1,1变成0 | |
左移 << |
按位左移运算符。左操作数按位左移右操作数指定的位数 | 1 << 2 = 4,即0100 |
右移 >> |
按位右移运算符。左操作数按位右移右操作数指定的位数 | 4 >> 2 = 1,0100 >> 2 = 0001 |
具体例子
按位存储不同的数值
看一段线程池原码,我们通过注解来分析
public class ThreadPoolExecutor extends AbstractExecutorService {
//这里其实就是保存了2个数值,低位和高位分别保存为两块领地,各自存储数据。
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// 这里定义一个界限,定义了一个integer字节的高3位和剩下的位两个领地,高3位存储的时候要跳过低位的领地
private static final int COUNT_BITS = Integer.SIZE - 3;
/**
* 这东西是给你来解码(取数据)用的, 1左移29位 = 00100000000000000000000000000000
* 再减去 1 = 00011111111111111111111111111111
* 这里刚好 高3位 为0,剩下的位为1,高低区域路径分明。
**/
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// Packing and unpacking ctl
/**
* 通过 c & CAPACITY 来取出低位的值(高3位都是0, &操作的时候 c的高3位就直接忽略)
* 通过 c & (~CAPACITY) 来取出高位的值(取反之后高3都是1,低29位是0 ,c的低29位就忽略了)
**/
private static int runStateOf(int c) { return c & ~CAPACITY; }
private static int workerCountOf(int c) { return c & CAPACITY; }
// runState is stored in the high-order bits
// -1 的补码是 11111111111111111111111111111111 左位移 29位,左位移右边补0. 那么高3 = 111,是个负数
private static final int RUNNING = -1 << COUNT_BITS;
// 0怎么移动,所有位都是0,那么高3 = 000
private static final int SHUTDOWN = 0 << COUNT_BITS;
// 下面三个都是正数(因为符号位是0)
// 这里一样的计算 高3 = 001
private static final int STOP = 1 << COUNT_BITS;
// 高3 = 010
private static final int TIDYING = 2 << COUNT_BITS;
// 高3 = 011
private static final int TERMINATED = 3 << COUNT_BITS;
// 通过或操作让低位和高位 二进制有数值的都同时保留。
private static int ctlOf(int rs, int wc) { return rs | wc; }
}
想一个字段存多个值。
类似于多选框,用户勾选了多少个状态字段。
比较常见的用字符串,逗号分隔。"1,2,3,4"
如果了解位运算,状态不是很多 这里也可以用位运算表示,一个整数代替多个状态
11111111111111111111111111111111
按int=32位来说,可以把每一位代表一种状态的选中(1)和非选中(0),为1代表这个状态位有选中,0代表没选。
一个int可以同时保存32个状态是否勾选的信息。
每一位代表这个位对应的一个状态,如果这32位有多个1,会代表多个状态,
每个独立的状态对应的数值都是1,其他是0的有
1<<0,1<<1,1<<2,1<<3,1<<4,…….1<<31 ,这32个
你只能存储这32个字段,其他数字代表的都是这32个状态的1组合。
例如 1和2,我们都选中的话 就是 0001|0010 = 0011 = 3;
0011 又能拆出 0001 + 0010 2个独立的状态。
一段例子来看。
public class TypeAndSize {
private static final int status_1 = 1 << 0;
private static final int status_2 = 1 << 1;
private static final int status_3 = 1 << 2;
private static final int status_4 = 1 << 3;
private List<Integer> bitToList(int num) {
List<Integer> result = new ArrayList<>();
for (int bit = 0; bit < Integer.SIZE; bit++) {
// 从第0位开始左移,这个数值当基准点一直判断到最大位
int referenceValue = 1 << bit;
// 这里数值都是正整数来讲的,基准点是左移递增的,如果基准点都大于实际存储的数字,说明num之后的高位都0不用对比了
// 如果小于0说明最高位32位肯定有数值,那么就不能跳出循环,只能对比全部位置了。
if ( num > 0 && referenceValue > num) {
break;
}
// 依次判断这个位有没有1, 如果有1的话 这个等式肯定相等,因为referenceValue其他位都是0
if ((referenceValue & num) == referenceValue) {
result.add(referenceValue);
}
}
return result;
}
private int listToBit(List<Integer> list) {
// | 代表有一个位子是1 都是1 ,把list里面所有传递过来的状态都 合并起来,
return list.stream().reduce(1, (a, b) -> a | b);
}
public static void main(String[] args) {
TypeAndSize obj = new TypeAndSize();
// 将多个状态合并成一个值
int status = obj.listToBit(Arrays.asList(status_1,status_2,status_3,status_4));
// 取出存储的都有哪些状态
obj.bitToList(status).forEach(System.out::println);
}
}