1. 程式人生 > >遍地都是的位運算,關鍵時刻竟然有妙用!

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

很多人都可能在面試的時候遇到過這樣一道題目:

有 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=1

4.右移運算子 >> 和左移運算子 <<: 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)
複製程式碼

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