遍地都是的位運算,關鍵時刻居然有妙用!

不少人均可能在面試的時候遇到過這樣一道題目:android

有 1000 個如出一轍的瓶子,其中有 999 瓶是普通的水,其中有一瓶含有劇毒(稀釋後仍然具備毒性),你只有 10 條小白鼠,它們在喝下毒藥後會立刻死去,怎樣利用它們在最短的時間內判斷出哪瓶是毒藥?面試

咱們都知道,在計算機語言當中,全部的數字最終都會轉化爲二進制進行計算,而二進制中每個「位」可以表示兩種狀態,它們分別是數字 0 和 1。bash

回到剛纔的題目,每條小白鼠的生和死的狀態均可以表示二進制中的一個「位」, 10 條小白鼠一共就能表示 1024 種組合狀態,所以這道題目一個解決思路就是,給這 1000 瓶水都按照二進制的格式標上記號(10 位二進制數就能標記所有),讓這 10 條小白鼠分別對應這十位二進制中的一位,而後將這十位二進制數中當前位上是 1 的水混合在一塊兒給對應此位的小白鼠喝,根據小白鼠的死亡狀況就能定位哪瓶水有毒。ui

從 MeasureSpec 中理解位運算

在 Android 開發中,咱們也時常見到位運算的身影。在進行自定義 View 的時候,都會用到 int makeMeasureSpec(int size, int mode) 方法去獲取 View 的尺寸和測量模式,那麼它是怎麼把兩個變量組裝成一個的呢?簡單地講就是用一個 32 位二進制數字中的高兩位來存儲測量模式 MeasureMode,用低 30 位來存儲尺寸 MeasureSize,MeasureSpec 是 android.view.View 類中的一個內部類,關鍵代碼以下:spa

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);
    }
}
複製代碼

位運算經常使用的操做符有如下幾種:3d

  1. 或運算符| : 0|0=0,0|1=1,1|1=1code

  2. 與運算符& : 0&0=0,0&1=0,1&1=1cdn

  3. 非運算符~ : ~0=1,~1=0blog

  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)
複製代碼

這時候回去理解文章開頭的面試題目是否是很容易了?

相關文章
相關標籤/搜索