遍地都是的位運算,關鍵時刻竟然有妙用!
很多人都可能在面試的時候遇到過這樣一道題目:
有 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,將他們進行或操作,得到的就是是兩者的疊加值。
位運算在實際開發中的使用
類似的,在日常開發中,我們也可以用位運算來簡化一些操作,假如服務端返回一個數字,可能存在幾種狀態疊加的情況(下圖),如果按照傳統的方法來處理將會很麻煩,這時候就需要利用位運算了。
我們可以新建一個 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)
複製程式碼