位掩码的思考
第一章 一堆废话
第01节 引子
最近两天在写代码的过程中,使用到了 定位功能,在集成了 三方SDK 的过程中,我发现,需要申请多个权限,多个权限全部通过的情况下,才能完成这个功能。
接下来,我一顿操作猛如虎,写了三个权限的申请。如: 我想要完成定位,我需要三个权限
- 精准定位
- 内部存储
- 后台弹窗
也就是说 我需要 三个权限,全部通过,我才能正常使用我的软件! 只要有一个没有通过,将无法使用。
我考虑到别人做权限校验的过程中,出现了 红叉 绿勾 标注清楚的情况。如图:
如果常规的做法很简单,多个 boolean 值,直接判断一下,就可以了。但是如果增加了条件呢?
如何处理,能否用一个数据去记录 当前哪些权限开启了,哪些权限未开启?
我在想 如果我写了一个 函数方法,用于校验所有的权限。checkPermissions()反馈一个值给外部调用者,
外部只需要通过一个值,就明确知晓什么权限开启了,什么权限未开启。
我也在想,如果这个值,可以通过网络传输 或者 本地数据库记录。能否用一个值,完整的记录下来这个内容呢?
以前的想法,很可能就是写一个boolean[]布尔数组,记录下来哪些权限是开启的,哪些权限是关闭的。
但是目前我就想使用一个数据值,去记录多个状态。 这里的想法!
突然!我想到了 Linux 操作系统 中 文件权限管理chmod 775 myscript.sh
我就想 我们能否用一个 整数,它的二进制位数,去表示当前的权限选择呢。0000_0000_0000_0000_1111_0101_0000_0000
这样的数据中,不就是 由0和1去表示的 权限位。(0是不允许,1是允许)
如果我最开始的时候,就约定了 3号位置代表是 “精确定位” 5号位置代表是 “内部存储” 6号位置代表是 “后台弹窗”
通过对应位置的 0 和 1 确定这个是否被授权。 好像也可以啊!
然后我将整个 0和1 组成的数据,转换成为一个十进制的数据,进行存储(数据库) 或者 传输(网络)相比之下是不是 比 传递一堆的数据更小更快!
好像是可以的!
通过前面的介绍,我试着找了相关的资料,知晓了这种方案,有个专属的名词 【位掩码】
专业的定义:位掩码(Bitmask)是计算机科学中一种利用二进制位(0 和 1)来高效记录、修改和查询多个开关状态(或布尔条件)的技术。
第02节 理解
既然前面知道了 位掩码,那么我们能不能更直观的从 人的角度,去看待应用位掩码,帮我们做事情呢?
为了更好的去学习 位掩码,我想到了一个相对而言,比较贴合的例子。
现实生活中,如果 您会下象棋,应该知道象棋一共有 32颗 棋子。 他们分别是
能否用一个 int 类型的数据,来表示整个棋盘中,棋子的存活呢? 存在是 1, 不存在是 0
因为 int 类型的数据正好是 32位,下面是我用 Java 代码,定义的数据。
为了方便于大家理解这段代码的意思,我采用 xlsx 来表示内容。
也就是说,我们采用给个位数,表示了每一颗 棋子的 存活状态。
第二章 案例代码
第01节 位掩码的工具类
包含有 5 个方法
| 序号 | 方法名称 | 方法作用 | 举例说明 |
|---|---|---|---|
| 1 | int addData(int currentMask, int targetData) | 添加数据 | 参数1:当前模板 参数2:十进制数 |
| 2 | int removeData(int currentMask, int targetData) | 删除数据 | 参数1:当前模板 参数2:十进制数 |
| 3 | boolean containsData(int currentMask, int targetData) | 判断模板中存在数据 | 参数1:当前模板 参数2:十进制数 |
| 4 | List<String> maskToAvailableList(int currentMask, Map<String, Integer> businessMap) | 转换 十进制 转 映射值 | 参数1:当前模板 参数2:映射表 |
| 5 | String to32BitString(int number) | 将十进制数据 转换为二进制字符串 | 参数:十进制数据 |
代码
importjava.util.ArrayList;importjava.util.List;importjava.util.Map;publicclassBitmaskUtil{/** * 1. 添加数据(将某位置为 1) * 对应业务:棋子入场、开启某权限、记录某数据存在 * 换算公式:currentMask | targetData */publicstaticintaddData(intcurrentMask,inttargetData){returncurrentMask|targetData;}/** * 2. 剔除数据(将某位置为 0) * 对应业务:棋子死亡离场、关闭某权限、擦除某数据 * 换算公式:currentMask & ~targetData */publicstaticintremoveData(intcurrentMask,inttargetData){returncurrentMask&~targetData;}/** * 3. 检查数据是否存在/存活(判断某位是否为 1) * 换算公式:(currentMask & targetData) == targetData */publicstaticbooleancontainsData(intcurrentMask,inttargetData){return(currentMask&targetData)==targetData;}/** * 4. 转换数据:【十进制整数 -> 存活数据列表】 * 传入标准的业务字典(配置表),解析出当前整数里到底活着哪些数据 * * @param currentMask 当前的十进制掩码 * @param businessMap 业务配置字典(Key为数据名称,Value为位权重如 1, 2, 4, 8...) */publicstaticList<String>maskToAvailableList(intcurrentMask,Map<String,Integer>businessMap){List<String>activeList=newArrayList<>();for(Map.Entry<String,Integer>entry:businessMap.entrySet()){if(containsData(currentMask,entry.getValue())){activeList.add(entry.getKey());}}returnactiveList;}/** * 5. 辅助工具:获取精细的 32 位二进制字符串(高位补0),便于调试观察 */publicstaticStringto32BitString(intnumber){returnString.format("%32s",Integer.toBinaryString(number)).replace(' ','0');}}第02节 映射规范类
importjava.util.LinkedHashMap;importjava.util.Map;/** * 映射的规范,业务规范 */publicclassSpecification{// 业务配置表publicstaticfinalMap<String,Integer>configurationTable=newLinkedHashMap<>();// ========================================================// 1. 黑方 16 枚棋子位定义(交换后:占据低 16 位,即 1 << 0 到 1 << 15)// ========================================================// --- 底线 (九个位置) ---publicstaticfinalintB_JU_LEFT=1<<0;// 黑左车publicstaticfinalintB_MA_LEFT=1<<1;// 黑左马publicstaticfinalintB_XIANG_LEFT=1<<2;// 黑左象publicstaticfinalintB_SHI_LEFT=1<<3;// 黑左士publicstaticfinalintB_JIANG=1<<4;// 黑将 (居中无左右)publicstaticfinalintB_SHI_RIGHT=1<<5;// 黑右士publicstaticfinalintB_XIANG_RIGHT=1<<6;// 黑右象publicstaticfinalintB_MA_RIGHT=1<<7;// 黑右马publicstaticfinalintB_JU_RIGHT=1<<8;// 黑右车// --- 炮线 (两个位置) ---publicstaticfinalintB_PAO_LEFT=1<<9;// 黑左炮publicstaticfinalintB_PAO_RIGHT=1<<10;// 黑右炮// --- 卒线 (五个位置) ---publicstaticfinalintB_ZU_1=1<<11;// 黑卒1publicstaticfinalintB_ZU_2=1<<12;// 黑卒2publicstaticfinalintB_ZU_3=1<<13;// 黑卒3 (居中)publicstaticfinalintB_ZU_4=1<<14;// 黑卒4publicstaticfinalintB_ZU_5=1<<15;// 黑卒5// ========================================================// 2. 红方 16 枚棋子位定义(交换后:占据高 16 位,即 1 << 16 到 1 << 31)// ========================================================// --- 底线 (九个位置) ---publicstaticfinalintR_JU_LEFT=1<<16;// 红左车publicstaticfinalintR_MA_LEFT=1<<17;// 红左马publicstaticfinalintR_XIANG_LEFT=1<<18;// 红左相publicstaticfinalintR_SHI_LEFT=1<<19;// 红左仕publicstaticfinalintR_SHUAI=1<<20;// 红帅 (居中无左右)publicstaticfinalintR_SHI_RIGHT=1<<21;// 红右仕publicstaticfinalintR_XIANG_RIGHT=1<<22;// 红右相publicstaticfinalintR_MA_RIGHT=1<<23;// 红右马publicstaticfinalintR_JU_RIGHT=1<<24;// 红右车// --- 炮线 (两个位置) ---publicstaticfinalintR_PAO_LEFT=1<<25;// 红左炮publicstaticfinalintR_PAO_RIGHT=1<<26;// 红右炮// --- 兵线 (五个位置) ---publicstaticfinalintR_BING_1=1<<27;// 红兵1publicstaticfinalintR_BING_2=1<<28;// 红兵2publicstaticfinalintR_BING_3=1<<29;// 红兵3 (居中)publicstaticfinalintR_BING_4=1<<30;// 红兵4publicstaticfinalintR_BING_5=1<<31;// 红兵5static{// === 填充黑方 ===configurationTable.put("黑左车",B_JU_LEFT);configurationTable.put("黑左马",B_MA_LEFT);configurationTable.put("黑左象",B_XIANG_LEFT);configurationTable.put("黑左士",B_SHI_LEFT);configurationTable.put("黑将",B_JIANG);configurationTable.put("黑右士",B_SHI_RIGHT);configurationTable.put("黑右象",B_XIANG_RIGHT);configurationTable.put("黑右马",B_MA_RIGHT);configurationTable.put("黑右车",B_JU_RIGHT);configurationTable.put("黑左炮",B_PAO_LEFT);configurationTable.put("黑右炮",B_PAO_RIGHT);configurationTable.put("黑卒1",B_ZU_1);configurationTable.put("黑卒2",B_ZU_2);configurationTable.put("黑卒3",B_ZU_3);configurationTable.put("黑卒4",B_ZU_4);configurationTable.put("黑卒5",B_ZU_5);// === 填充红方 ===configurationTable.put("红左车",R_JU_LEFT);configurationTable.put("红左马",R_MA_LEFT);configurationTable.put("红左相",R_XIANG_LEFT);configurationTable.put("红左仕",R_SHI_LEFT);configurationTable.put("红帅",R_SHUAI);configurationTable.put("红右仕",R_SHI_RIGHT);configurationTable.put("红右相",R_XIANG_RIGHT);configurationTable.put("红右马",R_MA_RIGHT);configurationTable.put("红右车",R_JU_RIGHT);configurationTable.put("红左炮",R_PAO_LEFT);configurationTable.put("红右炮",R_PAO_RIGHT);configurationTable.put("红兵1",R_BING_1);configurationTable.put("红兵2",R_BING_2);configurationTable.put("红兵3",R_BING_3);configurationTable.put("红兵4",R_BING_4);configurationTable.put("红兵5",R_BING_5);}}第03节 测试类
如何使用上述的几个方法呢?
importjava.util.List;publicclassDemo{publicstaticvoidmain(String[]args){intFULL=0XFFFF_FFFF;intZERO=0X0000_0000;// 在空棋盘当中, 放入了两个数据(红左车 和 红帅) 返回的结果是一个十进制的整数intaddData=BitmaskUtil.addData(ZERO,Specification.R_JU_LEFT+Specification.R_SHUAI);// 输出当前的十进制整数System.out.println("addData = "+addData);// 将十进制的整数, 转换成为二进制的数据(32位的0和1组成) 如果存在是1, 如果不存在是 0Stringstr32add=BitmaskUtil.to32BitString(addData);// 输出当前的二进制System.out.println("str32add = "+str32add);// 将当前的数据, 转换成为映射中存在的数据值, 从映射表当中查找 存在的数据值List<String>listAdd=BitmaskUtil.maskToAvailableList(addData,Specification.configurationTable);// 输出当前的映射值System.out.println("listAdd = "+listAdd);System.out.println("==================================");// 在满棋盘当中, 移除掉数据(红帅 和 红右士) 返回的结果是一个十进制的整数intremoveData=BitmaskUtil.removeData(FULL,Specification.R_SHUAI+Specification.R_SHI_RIGHT);// 输出当前的十进制System.out.println("removeData = "+removeData);// 将十进制的整数, 转换成为二进制的数据(32位的0和1组成) 如果存在是1, 如果不存在是 0Stringstr32remove=BitmaskUtil.to32BitString(removeData);// 输出当前的二进制System.out.println("str32remove = "+str32remove);// 将当前的数据, 转换成为映射中存在的数据值, 从映射表当中查找 存在的数据值List<String>listRemove=BitmaskUtil.maskToAvailableList(removeData,Specification.configurationTable);// 输出当前的映射值System.out.println("listRemove = "+listRemove);// 查找当前的数据中, 是否包含有 需要查找的数据booleancontainsData=BitmaskUtil.containsData(removeData,Specification.R_SHUAI);// 输出查找的结果System.out.println("containsData = "+containsData);}}第04节 最终输出结果
addData = 1114112 str32add = 00000000000100010000000000000000 listAdd = [红左车, 红帅] ================================== removeData = -3145729 str32remove = 11111111110011111111111111111111 listRemove = [黑左车, 黑左马, 黑左象, 黑左士, 黑将, 黑右士, 黑右象, 黑右马, 黑右车, 黑左炮, 黑右炮, 黑卒1, 黑卒2, 黑卒3, 黑卒4, 黑卒5, 红左车, 红左马, 红左相, 红左仕, 红右相, 红右马, 红右车, 红左炮, 红右炮, 红兵1, 红兵2, 红兵3, 红兵4, 红兵5] containsData = false第三章 后续操作
第01节 拓展
关于后续,如果作为一个应用而言,我们只需要1、拿着工具类, 2、根据自己的业务,自定义配置项,应该就可以完成大部分的 实际需求。
例如:
前面我定义的几个条件,当然可能会更多。
- 精准定位
- 内部存储
- 后台弹窗
就可以修改配置项,作为条件,直接采用工具类,应该是可以完成对应的 位掩码权限管理。
第02节 深思
方向1:
如果我们想要深入的理解,整个操作,那么可以专门去研究一下位运算的操作。 也就是关于工具类中的几个 运算符。方向2:
如果我没有那么多的条件选项,我该如何呢? 或者说,我改为 16位的 short 甚至是 8位的 byte 亦或者是 64 位的 long方向3:
当前我写的是 Java 的版本,如果我们操作的其他的语言呢? 例如 C语言,C++等。