遍地都是的位运算,关键时刻竟然有妙用!

很多人都可能在面试的时候遇到过这样一道题目:

有 1000 个一模一样的瓶子,其中有 999 瓶是普通的水,其中有一瓶含有剧毒(稀释后仍然具有毒性),你只有 10 条小白鼠,它们在喝下毒药后会马上死去,怎样利用它们在最短的时间内判断出哪瓶是毒药?

我们都知道,在计算机语言当中,所有的数字最终都会转化为二进制进行计算,而二进制中每一个“位”能够表示两种状态,它们分别是数字 0 和 1。

回到刚才的题目,每条小白鼠的生和死的状态都可以表示二进制中的一个“位”, 10 条小白鼠一共就能表示 1024 种组合状态,因此这道题目一个解决思路就是,给这 1000 瓶水都按照二进制的格式标上记号(10 位二进制数就能标记全部),让这 10 条小白鼠分别对应这十位二进制中的一位,然后将这十位二进制数中当前位上是 1 的水混合在一起给对应此位的小白鼠喝,根据小白鼠的死亡情况就能定位哪瓶水有毒。

从 MeasureSpec 中理解位运算

在 Android 开发中,我们也时常见到位运算的身影。在进行自定义 View 的时候,都会用到 int makeMeasureSpec(int size, int mode) 方法去获取 View 的尺寸和测量模式,那么它是怎么把两个变量组装成一个的呢?简单地讲就是用一个 32 位二进制数字中的高两位来存储测量模式 MeasureMode,用低 30 位来存储尺寸 MeasureSize,MeasureSpec 是 android.view.View 类中的一个内部类,关键代码如下:

public static class MeasureSpec {
    private static final int MODE_SHIFT = 30;
    //SpecMode 掩码,用于屏蔽高两位
    //11 000000 00000000 00000000 00000000
    private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
    //00 000000 00000000 00000000 00000000
    public static final int UNSPECIFIED = 0 << MODE_SHIFT;
    //01 000000 00000000 00000000 00000000
    public static final int EXACTLY     = 1 << MODE_SHIFT;
    //10 000000 00000000 00000000 00000000
    public static final int AT_MOST     = 2 << MODE_SHIFT;

    //获取 MeasureSpec
    public static int makeMeasureSpec(int size, int mode) {
        // API 17 之前,忽略此条件
        if (sUseBrokenMakeMeasureSpec) {
            return size + mode;
        } else {
            return (size & ~MODE_MASK) | (mode & MODE_MASK);
        }
     }

    //获取 SpecMode
    public static int getMode(int measureSpec) {
        return (measureSpec & MODE_MASK);
    }

    //获取 SpecSize
    public static int getSize(int measureSpec) {
        return (measureSpec & ~MODE_MASK);
    }
}

位运算的操作符有以下几种:

  1. 或运算符| :0|0=0,0|1=1,1|1=1

  2. 与运算符& :0&0=0,0&1=0,1&1=1

  3. 非运算符~ : ~0=1,~1=0

  4. 异或运算符^ : 相同为 0,不同为 1:0^0=0,1^0=1,0^1=1,1^1=0

  5. 右移运算符 >> 和左移运算符 << :001<<2=100,110>>1=11

在 MeasureSpec 类中,getMode 方法是将参数 measureSpec 与 MODE_MASK 进行与运算,MODE_MASK 可以理解为 SpecMode 的掩码,运算的结果是保留measureSpec 的高两位,剩下的后 30 位置 0,得到的是 MeasureMode。

getSize 方法是先将 MODE_MASK 取反再跟 measureSpec 进行与运算,结果是高两位为 0 低 30 位不变的值,即 SpecSize。

makeMeasureSpec 方法中,size & ~MODE_MASK 的结果是 size 的 SpecSize,mode & MODE_MASK 的结果是 SpecMode,将他们进行或操作,得到的就是是两者的叠加值。

位运算在实际开发中的使用

类似的,在日常开发中,我们也可以用位运算来简化一些操作,假如服务端返回一个数字,可能存在几种状态叠加的情况(下图),如果按照传统的方法来处理将会很麻烦,这时候就需要利用位运算了。


status.png

我们可以新建一个 StatusManager 类用来处理这个复杂的状态:

public class StatusManager {
    // 正常
    public static final int STATUS_NORMAL = 0 ; // 0000

    //时间同步失败
    public static final int STATUS_TIME_ASY = 1 ; // 0001

    // 开门指令失败
    public static final int STATUS_OPEN_DOOR = 1 << 1; // 0010

    // 添加固定密码失败
    public static final int STATUS_ADD_FIXED_PSW = 1 << 2; // 0100

    // 删除固定密码失败
    public static final int STATUS_DEL_FIXED_PSW = 1 << 3; // 1000

    // 存储目前的权限状态
    private int flag;

    /**
     *  重置状态
     */
    public void setStatus(int status) {
        flag = status;
    }

    /**
     *  添加一种或者多种状态
     */
    public void addStatus(int status) {
        flag |= status;
    }

    /**
     *  删除一种或者多种状态
     */
    public void deleteStatus(int status) {
        flag &= ~status;
    }

    /**
     *  是否具有某些状态
     */
    public boolean hasStatus(int status) {
        return (flag & status) == status;
    }

    /**
     *  是否不具有某些状态
     */
    public boolean isHasnotStatus(int status) {
        return (flag & status) == 0;
    }

    /**
     *  是否仅仅具有某些状态
     */
    public boolean isOnlyHas(int status) {
        return flag == status;
    }
}

添加状态时,可以这样写:

manager.addStatus(StatusManager.STATUS_TIME_ASY | STATUS_ADD_FIXED_PSW )

如果需要判断是否时间同步和开门指令同时失败,可以这样写:

manager.hasStatus(StatusManager.STATUS_TIME_ASY | STATUS_OPEN_DOOR)

这时候回去理解文章开头的面试题目是不是很容易了?

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,384评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,845评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,148评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,640评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,731评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,712评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,703评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,473评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,915评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,227评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,384评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,063评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,706评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,302评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,531评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,321评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,248评论 2 352

推荐阅读更多精彩内容

  • 第2章 基本语法 2.1 概述 基本句法和变量 语句 JavaScript程序的执行单位为行(line),也就是一...
    悟名先生阅读 4,145评论 0 13
  • Android在编码的时候经常使用到位运算,这里以Intent的Flags为例。(查看Intent说明文档) 首先...
    离人歌阅读 1,887评论 0 4
  • 运算符是处理数据的基本方法,用来从现有的值得到新的值。JavaScript 提供了多种运算符,本章逐一介绍这些运算...
    徵羽kid阅读 676评论 0 0
  • 运算符是处理数据的基本方法,用来从现有的值得到新的值。JavaScript 提供了多种运算符,本章逐一介绍这些运算...
    许先生__阅读 602评论 0 3
  • 我与住医院的母亲 母亲与我一家的合影 母亲在病重期间县领导前来看望 在辛卯年(二零一二年一月二...
    刘爱国阅读 374评论 6 6