一段位运算代码的理解记录
平时位运算、二进制操作什么的用到的很少,最近工作中要为 Flutter 的 APP 对接一个 BLE 设备,其中涉及到了相关的内容,简单记录下对一段代码的理解
代码片段
1 | public static int getBytesIndex(int bitPos) { |
本段代码的意图:
- 传入的 data 的长度为20,记为 d0 ~ d19;
- 最终将产生12个 point 数据,每个 point 共 13bit 有效数据,记为 p0 ~ p11;
- d0 与 d1 视为第一组,其中:
- d0 的第 4~7 位为标识和序号;
- 第 0~3 位分别为 p3 ~p0 的最高位;
- d1 的第 0~7 位分别为 p7 ~p11 的最高位;
- 后续 data 每三个为一组循环,以 d2 ~ d4 为例,其中:
- d2 为 p0 的低8位
- d4 为 p1 的低8位
- d3 的高4位(第 4~7 位)为 p0 的第 8~11位
- d3 的低4位(第 0~3 位)为 p1 的第 8~11位
所以去掉 d0 和 d1,剩下数据3个一组共有6组,每组产生两个 point,最终是12个 point,每个 point 的长度为 8 + 4 + 1 = 13。
逐行分析
首先是两个定位函数
由于每个 data 的长度为 8bit,所以20个 data 共计 160bit,那么对于传入的 bitPos:
1 | // 整除长度 8 得到该bit位于data中的第几个byte |
对应如下:
bitPos: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
bitVal: | 1 | 0 | 0 | 1 | 0 | 1 | 1 | 0 | 1 | 0 | 0 | 1 | 0 | 1 | 1 | 0 |
bit: | 7bit | 6bit | 5bit | 4bit | 3bit | 2bit | 1bit | 0bit | 7bit | 6bit | 5bit | 4bit | 3bit | 2bit | 1bit | 0bit |
offset: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
byte: | <= | = | = | d | 0 | = | = | => | <= | = | = | d | 1 | = | = | => |
然后分析主解析逻辑:
1 | private int[] parserHeartData(byte[] data) { |
tem >> 7 - getOffset(i) & 1
可以获得预期结果,是因为运算优先级 加减操作 高于 位移 高于 按位与;- 由于(byte)1的二进制为
0000 0001
,所以任何数据 &1后,逐位与比较,高位全部因为和0与而变成0,最低位和1与可以保持该bit,所以可以实现提取数组的最低位;- 之后代码中的
& 15
同理,因为(byte)15的二进制为0000 1111
,所以数据&15 后得到的是该数据的低4位。
1 | // 接下来的这两行,先是定义了长度为12的数组,用于存储最终的 points,然后将脚标重置为零 |
1.由于左移操作后,数据的低位都将置为0,例如
1010 1101 << 3
的结果为0110 1000
,按位或操作遇到 0 可以保留与这个 0 或的 bit 值,所以samp[pIndex] << 12(tem 和 high4 的长度 ) | high4 << 8(tem 的长度) | tem
就完成了bit的组装,得到了最终的数据
2.根据 程序加法运算的二进制原理 很容易可以看出,如果不是用 | 操作。而是用 + 操作将三个部分合起来,实际指令的数量应该是一样的;但是这里还是应该用 | 操作,还是因为运算优先级 加减操作 高于 位移 高于 按位或,如果用 + 则需要把上面的代码改为(samp[pIndex] << 12) + (low4 << 8) + tem
Android 中常见的用法
关于 |
操作符号,在 Android 开发中经常用得到,比如:
1 | Intent intent = new Intent(); |
addFlags(int flag)
方法的参数只有一个而且类型为 int,而有时需要设置多个 flag,此时就会用到 |
操作服将两个 flag 合并,之所以能够这样操作,是因为 Android SDK 中判断设置的 flag 就是判断的 bit 位。以上面的代码为例,Intent.FLAG_ACTIVITY_CLEAR_TASK
的值为 32768
, 二进制为 1000 0000 0000 0000
;而Intent.FLAG_ACTIVITY_NEW_TASK
的值为 268435456
, 二进制为 0001 0000 0000 0000 0000 0000 0000 0000
;所以 Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK
的值为 0001 0000 0000 0000 1000 0000 0000 0000
类似的操作在各种配置判断的情况下都很常见,qt中也有类似的用法