位运算的那些事系列:

  • 《位运算的那些事(一)搞懂机器码》
  • 《位运算的那些事(二)如何位运算》
  • 《位运算的那些事(三)位掩码》

前两篇我重点针对位运算基础以及运算过程详细的进行了讲解说明,相信看过的小伙伴也都很明了了。那么基础有了,也知道运算过程了,那我们常见的战场在哪里呢?这就像排兵布阵一样,只阅读兵法,而没有实践和模拟,只能算纸上谈兵了。本篇就拉开帷幕直面开发中这个最常见的战场——位掩码(BitMask)。

什么是掩码

说起掩码大家都听过子网掩码吧,子网掩码的主要作用是判断当前IP是属于什么样的网络,是A类还是B类还是C类;当前IP处于什么样的网段,网段内可以拥有多少个机子。比如我们公司电脑的子网掩码是255.255.255.0,很明显就是一个局域网。如果你对子网掩码还是不清晰,可以看一下《如何理解子网掩码》。

掩码就是一串二进制代码对目标字段进行位与运算,屏蔽当前的输入位,最终得到一个合理的需求。说白了,掩码就是一把辅助钥匙,你给我一个盒子我帮助你打开看看里面是什么

说到这不知道大家有没有对掩码有一个概念性的认识呢?不清楚没关系,这只是位运算中一个插曲,下边的讲解中也会相应用到,到时候你就明白了,本篇的目的是为了讲位运算在项目开发中的一些典型用法。

抛砖引玉

有一个很经典的算法题,说是有1000个一模一样的瓶子,其中有999瓶是普通的水,有一瓶是毒药。任何喝下毒药的生物都会在一星期之后死亡。现在,你只有10只小白鼠和一星期的时间,如何检验出哪个瓶子里有毒药?如果按照常规的解法是不是很繁琐,我们不妨思考一下用二进制来处理。

具体实现跟3个老鼠确定8个瓶子原理一样:

000=0
001=1
010=2
011=3
100=4
101=5
110=6
111=7

一位表示一个老鼠,0-7表示8个瓶子。也就是分别将1、3、5、7号瓶子的药混起来给老鼠1吃,2、3、6、7号瓶子的药混起来给老鼠2吃,4、5、6、7号瓶子的药混起来给老鼠3吃,哪个老鼠死了,相应的位标为1。如老鼠1死了、老鼠2没死、老鼠3死了,那么就是101=5号瓶子有毒。同样道理10个老鼠可以确定1000个瓶子。

经典场景

在开发过程中,有些时候我们要定义很多种状态标,举一个经典的权限操作的例子(来源于网上),假设这里有四种权限状态如下:

public class Permission {// 是否允许查询private boolean allowSelect;// 是否允许新增private boolean allowInsert;// 是否允许删除private boolean allowDelete;// 是否允许更新private boolean allowUpdate;
}

我们的目的是判断当前用户是否拥有某种权限,如果单个判断好说,也就四种。但如果混合这来呢,就是2的4次方,共有16种,这就繁琐了。那如果有更多权限呢?组合起来复杂度也就成倍往上升了。

应用分析

还是拿上边的权限例子来说事,我们改造一下,运用二进制移位来表示:

public class NewPermission {// 是否允许查询,二进制第1位,0表示否,1表示是public static final int ALLOW_SELECT = 1 << 0; // 0001// 是否允许新增,二进制第2位,0表示否,1表示是public static final int ALLOW_INSERT = 1 << 1; // 0010// 是否允许修改,二进制第3位,0表示否,1表示是public static final int ALLOW_UPDATE = 1 << 2; // 0100// 是否允许删除,二进制第4位,0表示否,1表示是public static final int ALLOW_DELETE = 1 << 3; // 1000// 存储目前的权限状态private int flag;/***  重新设置权限*/public void setPermission(int permission) {flag = permission;}/***  添加一项或多项权限*/public void enable(int permission) {flag |= permission;}/***  删除一项或多项权限*/public void disable(int permission) {flag &= ~permission;}/***  是否拥某些权限*/public boolean isAllow(int permission) {return (flag & permission) == permission;}/***  是否禁用了某些权限*/public boolean isNotAllow(int permission) {return (flag & permission) == 0;}/***  是否仅仅拥有某些权限*/public boolean isOnlyAllow(int permission) {return flag == permission;}
}

上边代码就是抛开常规的状态表示法(移位表示),例如:

ALLOW_SELECT = 1 << 0,转成二进制就是0001,二进制第一位表示Select权限。
ALLOW_INSERT = 1 << 1,转成二进制就是0010,二进制第二位表示Insert权限。
ALLOW_UPDATE = 1 << 2,转成二进制就是0100,二进制第三位表示Update权限。
ALLOW_DELETE = 1 << 3,转成二进制就是1000,二进制第四位表示Delete权限。

你会发现上边四种权限表示都有一个特点,那就是转化成二进制中的“1”只占用其中的某一位,其余的全部都是0,这就为接下来的位运算提供了极大的便利。我们用一个全局的整形变量flag来存储各种权限的启用和停用状态,那么得到的二进制结果中每一位的0或1都代表当前所在位的权限关闭和开启,四种权限有16种组合方式,下边就列举一部分,大家可以看一下:

flag 查询 新增 修改 删除 说明
1(0001) 0 0 0 1 只允许查询(即等于ALLOW_SELECT)
2(0010) 0 0 1 0 只允许新增(即等于ALLOW_INSERT)
4(0100) 0 1 0 0 只允许修改(即等于ALLOW_UPDATE)
8(1000) 1 0 0 0 只允许删除(即等于ALLOW_DELETE)
3(0011) 0 0 1 1 只允许查询和新增
12(1100) 1 1 0 0 只允许修改和删除
0(0000) 0 0 0 0 都不允许
15(1111) 1 1 1 1 全都允许

四种权限有16种组合方式,这16种组合方式就都是通过位运算得来的,其中参与位运算的每个因子你都可以叫做掩码(MASK),例如我要查询是否有修改和删除的权限我可以这样:

if (permission.isAllow(NewPermission.ALLOW_UPDATE | ALLOW_DELETE)){...
}

当然我也可以定义一个isAllowUpdateDelete()这样的方法,这样处理:

// 定义拥有修改和删除权限的mask
private static final int ALLOW_UPDATE_DELETE_MASK = 12;
// 是否拥有修改和删除的权限
public boolean isAllowUpdateDelete(){return flag & ALLOW_UPDATE_DELETE_MASK;
}...// 用的时候这样既可
if (permission.isAllowUpdateDelete()){...
}

代码中的常量ALLOW_UPDATE_DELETE_MASK就是我们定义的拥有某些操作的掩码,这在Android源码也是很常见的,这样处理我们就不用建立List或者专门遍历判断一些相关权限了。

至此应该对掩码有一个清楚的了解了吧,那位掩码(BitMask)是什么呢?

BitMask并不是一个类,也不是某种特殊的单位,它更像是一种思想。在BitMask中,使用一个数值来记录各种状态的集合,使用这个数值的每一位来表达每一种状态。在Android中,一个普通的int类型,是32位,则可以表达32中不同的状态而互不影响。

其实在开发过程中除了移位表示标识,大部分采用的是十六进制表示,还有十六进制和移位混合形式,这些在一些系统源码中普遍体现。

源码实例

在Android源码中主要针对FLAG的运算有三种:

1.增加属性 “|” 。

如果需要向flag变量中增加某个FLAG,使用"|"运算符 flag |= XXX_FLAG;

原因: 如果flag变量没有XXX_FLAG,则“|”完后flag对应的位的值为1,如果已经有XXX_FLAG,则“|”完后值不会变,对应位还是1。

2.包含属性 “&” 。

如果需要判断flag变量中是否包含XXX_FLAG,使用"&"运算符,flag & XXX_FLAG != 0 或者 flag & XXX_FLAG = XXX_FLAG。

原因: 如果flag变量里包含XXX_FLAG,则“&”完后flag对应的位的值为1,因为XXX_FLAG的定义保证了只有一位非0,其他位都为0,所以如果是包含的话进行“&”运算后值不为0,该位上的值为此XXX_FLAG的所在位上的值,不包含的话值为0。

3.去除属性 “&~” 。

如果需要去除flag变量的XXX_FLAG, 使用 “&~”, flag &= ~XXX_FLAG;

原因: 先对XXX_FLAG进行取反则XXX_FLAG原来非0的那一位变为0,然后使用“&”运算后如果flag变量非0的那一位变为0,则意味着flag变量不包含XXX_FLAG。

Configuration 类

比如Android源码中的Configuration类。Configuration类专门描述手机设备上的配置信息,包括屏幕旋转、屏幕方向、字体设置、缩放因子、软键盘、移动信号等等,因此有很多种状态配置,以下是部分配置:

    /** Constant for {@link #colorMode}: bits that encode whether the screen is wide gamut. */public static final int COLOR_MODE_WIDE_COLOR_GAMUT_MASK = 0x3;/*** Constant for {@link #colorMode}: a {@link #COLOR_MODE_WIDE_COLOR_GAMUT_MASK} value* indicating that it is unknown whether or not the screen is wide gamut.*/public static final int COLOR_MODE_WIDE_COLOR_GAMUT_UNDEFINED = 0x0;/*** Constant for {@link #colorMode}: a {@link #COLOR_MODE_WIDE_COLOR_GAMUT_MASK} value* indicating that the screen is not wide gamut.* <p>Corresponds to the <code>-nowidecg</code> resource qualifier.</p>*/public static final int COLOR_MODE_WIDE_COLOR_GAMUT_NO = 0x1;/*** Constant for {@link #colorMode}: a {@link #COLOR_MODE_WIDE_COLOR_GAMUT_MASK} value* indicating that the screen is wide gamut.* <p>Corresponds to the <code>-widecg</code> resource qualifier.</p>*/public static final int COLOR_MODE_WIDE_COLOR_GAMUT_YES = 0x2;/** Constant for {@link #colorMode}: bits that encode the dynamic range of the screen. */public static final int COLOR_MODE_HDR_MASK = 0xc;/** Constant for {@link #colorMode}: bits shift to get the screen dynamic range. */public static final int COLOR_MODE_HDR_SHIFT = 2;/*** Constant for {@link #colorMode}: a {@link #COLOR_MODE_HDR_MASK} value* indicating that it is unknown whether or not the screen is HDR.*/public static final int COLOR_MODE_HDR_UNDEFINED = 0x0;/*** Constant for {@link #colorMode}: a {@link #COLOR_MODE_HDR_MASK} value* indicating that the screen is not HDR (low/standard dynamic range).* <p>Corresponds to the <code>-lowdr</code> resource qualifier.</p>*/public static final int COLOR_MODE_HDR_NO = 0x1 << COLOR_MODE_HDR_SHIFT;/*** Constant for {@link #colorMode}: a {@link #COLOR_MODE_HDR_MASK} value* indicating that the screen is HDR (dynamic range).* <p>Corresponds to the <code>-highdr</code> resource qualifier.</p>*/public static final int COLOR_MODE_HDR_YES = 0x2 << COLOR_MODE_HDR_SHIFT;

Configuration类标识这设备的详细信息,但是源码编写者也不可能把每一个很微小的细节都标识进来,这样就太庞大了,他们会把基本使用标识进来,然后在定义一些场景掩码(_MASK),通过这些场景掩码在代码逻辑中进行位掩码实现所需要的功能:

    /*** Return whether the screen has a round shape. Apps may choose to change styling based* on this property, such as the alignment or layout of text or informational icons.** @return true if the screen is rounded, false otherwise*/public boolean isScreenRound() {return (screenLayout & SCREENLAYOUT_ROUND_MASK) == SCREENLAYOUT_ROUND_YES;}/*** Return whether the screen has a wide color gamut and wide color gamut rendering* is supported by this device.** @return true if the screen has a wide color gamut and wide color gamut rendering* is supported, false otherwise*/public boolean isScreenWideColorGamut() {return (colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK) == COLOR_MODE_WIDE_COLOR_GAMUT_YES;}/*** Return whether the screen has a high dynamic range.** @return true if the screen has a high dynamic range, false otherwise*/public boolean isScreenHdr() {return (colorMode & COLOR_MODE_HDR_MASK) == COLOR_MODE_HDR_YES;}

View绘制过程中onMeasure的参数

在自定义view中我们常常实现三种方法,其中有一个onMeasure方法,主要用于view绘制过程中的一个测量,Android开发的同学这点很清楚:

    @Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);}

其入参中有两个参数“widthMeasureSpec”、“heightMeasureSpec”。这两个参数都是32位int值,其中高2位是SpecMode(测量模式),低30位是SpecSize(在某种测量模式下,所测得的精确值)。

针对测量模式,系统预制了三种:

    /*** Measure specification mode: The parent has not imposed any constraint* on the child. It can be whatever size it wants.*/public static final int UNSPECIFIED = 0 << MODE_SHIFT;/*** Measure specification mode: The parent has determined an exact size* for the child. The child is going to be given those bounds regardless* of how big it wants to be.*/public static final int EXACTLY     = 1 << MODE_SHIFT;/*** Measure specification mode: The child can be as large as it wants up* to the specified size.*/public static final int AT_MOST     = 2 << MODE_SHIFT;

那我们在平时开发中如何取view的精确值(宽、高)呢,按理说只需要取后30位的值即可,左移两位。如果用api去处理:MeasureSpec.getSize(widthMeasureSpec),然后我们深入系统源码看一下系统是如何运作的:

    /*** Extracts the size from the supplied measure specification.** @param measureSpec the measure specification to extract the size from* @return the size in pixels defined in the supplied measure specification*/public static int getSize(int measureSpec) {return (measureSpec & ~MODE_MASK);}

这里就清楚了,原来系统也是用位掩码处理的,我们再看一下掩码MODE_MASK是怎么表示的:

    private static final int MODE_SHIFT = 30;private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

看到有同学可能会问为什么是0x3呢?当你想到上边的三种模式,不由的惊喜,原来MODE_MASK = UNSPECIFIED | EXACTLY | AT_MOST。MODE_MASK左移30位刚好是view的SpecMode,然后measureSpec再将SpecMode去除,刚好就是我们想要的SpecSize。

一个小问题

上边也提到开发过程中针对位掩码这些FLAG,会用到移位表示法、十六进制表示法、混合表示法,但十六进制表示法更为常见,那么这里抛出一个小问题:为什么开发普遍用十六进制来定义FLAG?

其实开发过程中不固定使用哪种进制,8进制的也有用到,但是最终回归到的都是二进制,开发者普遍用十六进制主要是编码习惯和更为方便,具体原因个人总结有两条:

  1. 缩短编写空间,总不能用二进制32个1或者0来定义一个整形常量吧。
  2. 十六进制更容易转化成二进制,因此在代码阅读和逻辑分析尤其是运用在位运算上更有优势。

其他用法

1.判断int型变量a是奇数还是偶数

a&1 = 0 偶数
a&1 = 1 奇数

2.整数的平均值

对于两个整数x,y,如果用 (x+y)/2 求平均值,会产生溢出,因为 x+y 可能会大于INT_MAX,但是我们知道它们的平均值是肯定不会溢出的,我们用如下算法:

public int average(int x, int y){ return (x&y)+((x^y)>>1);
}

3.判断一个正整数是不是2的幂

public boolean power2(int x) { return ((x&(x-1))==0)&&(x!=0);
}

总结

到此针对位运算的相关知识点终于完了,从起初的机器码,到位运算规则,再到本篇的实用战场,相信读过这三篇的小伙伴一定有很大收获。

在开发过程中运用位运算,有些时候可以极好的缩短编写空间和良好的程序扩展性,但是并不是说位运算就是最好的,毕竟代码是写给人看的,我们的代码要有可读性可持续维护性,所以在开发过程中针对场景的不用,运用的策略也不同,避免滥用,良好运用。

最后

我是i猩人,总结不易,转载注明出处,喜欢本篇文章的童鞋欢迎点赞、关注哦。

位运算的那些事(三)位掩码相关推荐

  1. php二进制应用位运算,【转】PHP 位运算应用口诀

    标签: 位运算应用口诀 清零取位要用与,某位置一可用或 若要取反和交换,轻轻松松用异或 移位运算 要点 1 它们都是双目运算符,两个运算分量都是整形,结果也是整形. 2 "< 3 &q ...

  2. c语言10以内位运算,C语言基础知识--位运算

    1.原码,反码,补码: (1)在n位的机器数中,最高位为符号位,该位为零表示为正,为一表示为负:其余n-1位为数值位,各位的值可为零或一.当真值为正时,原码.反码.补码数值位 完全相同:当真值为负时, ...

  3. matlab求一个数的位数字,matlab求一个三位整数各位数字的立方和等于该数本身则称为...

    用C语言随机产生一个三位整数 思路:分别产生个.十.百位上的随机数,依次组合在一起#include#include#includeintmain(){inti,tmp;num=0;srand((uns ...

  4. 位运算全面总结,关于位运算看这篇就够了

    文章目录 1 位运算概述 2 位运算的性质 2.1 运算符的优先级 2.2 位运算符的运算律 3 位运算高级操作 4 负数的位运算 5 位运算的一些应用 6 位运算例题 6.1 更新二进制位 6.2 ...

  5. 位运算判断奇偶数_位运算判断奇偶数

    第一条:利用位运算判断一个整数是奇数还是偶数. 经常用到一个for循环,当索引i是奇数时执行语句A,偶数时执行语句B.判断i是奇数还是偶数,可用如下方法: if(i &1){ //i是奇数情况 ...

  6. 拆位 ---- C. Johnny and Another Rating Drop[位运算,计算每一位的贡献+推导过程]

    C. Johnny and Another Rating Drop 题目大意:就是定义一个不公平度就是相邻两个数的二进制位不相同数 比如说:100和011就是有3位不同 现在给你一个n∈[1,1e18 ...

  7. html 账号只显示前3位,只知道手机号码前三位还有名字能查到全部号码 – 手机爱问...

    2018-04-01 新浪的地址是什么呢? 请注意:如果您的邮箱已经开始收费,退订成功后,从下个月起将不会从您的手机上再次扣费.退订后,您的收费邮箱到期后将不能使用,所以请您一定要做好备份. 退订请您 ...

  8. python中整数逆位运算_python变量运算符和位运算

    1-1 python注释 #这是一个注释 print("Hello world") #Hello world 1-2 python运算符 转义字符 1-3Python 变量类型 创 ...

  9. 位运算判断奇偶数_位运算符判断奇偶

    之前在学习Java时其实已经踩过一次坑,这次又忘了.再次记录一下这个小问题 我们在判断奇偶时,除了最常用的%2,还可以用位运算符&去判断,但是有时会忘了加上括号,导致结果出错. 如图:本意想让 ...

最新文章

  1. oracle视图(转)
  2. 【二叉树详解】二叉树的创建、遍历、查找以及删除等-数据结构05
  3. 数学建模算法:支持向量机_从零开始的算法:支持向量机
  4. Linux下文件的多进程拷贝
  5. 支持向量机SVM算法原理及应用(R)
  6. Java 8 的List<V> 转成 Map<K, V>
  7. NoHttp开源Android网络框架1.0.0之架构分析
  8. Django1.7如何配置静态资源访问
  9. Python 中的黑暗角落(三):模块与包
  10. linux进程跑飞了,【Shell】Linux信号(二)
  11. 浅谈CDQ分治与偏序问题
  12. php 数组的深度,有没有办法找出PHP数组的“深度”?
  13. 提高Eclipse的运行速度 去掉JPA这个Eclipse 插件
  14. 大吉大利,今晚如何用R语言解锁“吃鸡”正确姿势
  15. 转:git设置过滤忽略的文件或文件夹
  16. cass软件yy命令_南方CASS软件快捷命令大全,高手必备。。。
  17. 迅为RK3399开发板嵌入式linux开发指南
  18. 淘宝APP用户行为数据分析案例——Python
  19. 谷歌学术首页url爬取
  20. u盘修复计算机系统,如何使用u盘修复系统

热门文章

  1. java网络编程实用精解_Java网络编程实用精解
  2. 吱口令代付|淘宝天猫教程|找人代付|淘宝代付源码
  3. android三星定位闪退,三星手机闪退问题7种修复方法
  4. CMOS图像传感器中的噪声来源分析
  5. Modbus协议(翻自wiki)
  6. 3d抽奖html,3d抽奖(微信)
  7. html+dom+chm,HTML DOM getElementsByClassName() - JavaScript - 菜鸟学堂-脚本之家
  8. 超导量子计算机最新报道,量子效应的量子计算机,在高温超导体加持下,或将迎来重大突破!...
  9. 关于母亲节的c语言程序设计教程课后答案,《我的母亲》习题及参考答案
  10. 七、手写实现决策树算法