Java使用位域進行多標記(狀態)管理

jimmie_yang發表於2018-11-01

Android中位域的應用

在Android中,我們會經常用到或者看到以下這樣的程式碼 :

public class ExampleUnitTest {
    @Test
    public void gravityTest(LayoutParams params) {
        // 檢視在layout中右下角顯示
        params.gravity = Gravity.RIGHT | Gravity.BOTTOM;
    }
    @Test
    public void intentFlagTest(Intent intent) {
        // 清空任務棧中所有舊的activity
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                | Intent.FLAG_ACTIVITY_CLEAR_TASK);

        // 如果activity已存在於棧中,清空該activity之上的所有任務
        intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP
                | Intent.FLAG_ACTIVITY_CLEAR_TOP);
    }
    @Test
    public void windowMangerFlags(WindowManager.LayoutParams params) {
        // 不攔截檢視以外的事件,在鎖屏中顯示
        params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
    }
}

通過一個 int欄位,來新增多個 標誌或者狀態. 一個int欄位,能夠管理多個標記(狀態)值.
如此神奇的操作怎樣實現的呢? 答案就是通過位運算來實現.

位操作基礎

java中提供的基礎位運算子有 與(&),或(|),非(~),異或(^),左移<<,
右移(>>)無符號右移(>>>).

除了位非(~)是一元操作符外,其它的都是二元操作符。

下面只介紹本文中,使用到的位操作

  • 按位與

A & B : A和B對應的二進位制數位都為1時,結果才為1,其他情況為0.

A     =  001101 // 13
B     =  100101 // 37
A & B =  000101 // 5
  • 按位或

A | B : A和B對應的二進位制數位都為0時,結果才為0,其他情況為1.

A     =  001101 // 13
B     =  100101 // 37
A | B =  101101 // 45
  • 按位非

~A : 將a的二進位制表示每一位進行取反操作,0變1,1變0.
相當於相反數 – 1

A     =  001101 // 13
~A    =  11111111111111111111111111110010 // int32位,補碼錶示,第一位為符號位
// 根據上訴補碼轉原碼為
//       10000000000000000000000000001110 // -14
  • 左移操作

A << B:將A的二進位制表示的每一位向左移B位,左邊超出的位截掉,右邊不足的位補0。
在取值範圍內,移動一位相當於乘2.

A     =  001101 // 13
A << 1 = 011010 // 26

原理與實踐

通常情況下,如果多個狀態或者標記相互之間有關聯, 如佈局方向,上下左右,左上,居中 … 等.我們可能會為每一個標記設定一個變數.

boolean left = false;
boolean right = false;
...
void setLeft(boolean b);
void setRight(boolean b);
...

這種情況下, 有4個標記相互關聯,並且能產生新的標記,那麼我們就需要設定4個標記變數,然後只能通過一系列的set方法來轉換狀態.如

v.setLeft(true);
v.setRight(true);

這樣就會使得,各個狀態不易維護和判斷,狀態越多,情況越複雜,程式碼會顯得冗長難以維護.

像這種,獨立狀態(標記)之間相互組合可以產生新的狀態(標記),且每個獨立狀態(標記)只有true或者false值的,我們可以使用位域的概念來管理這些狀態.

它的核心思想就是將, int 數值看做是 二進位制數位表示.如果有四個狀態就可以像這樣 0000,用四位二進位制表示,每一個二進位制位都可以表示一種狀態. 然後通過 位運算,來提取或新增標記位.四位對應的組合狀態有16個. 而我們,只需要通過一個int變數就能夠管理這些狀態.

當參與的狀態(標記)越多時,如果使用單獨的標記變數,就需要生成越多的變數,而用位域,這種獨立狀態為不管有多少個,都可以用一個變數表示.int型別最多存放32個獨立狀態

下面我們來看具體實現.(簡單的模仿Gravity類的一部分功能)

public class Gravity {
    // 二進位制表示 0001
    public static final int LEFT              = 1;
    // 二進位制表示 0010
    public static final int RIGHT             = LEFT << 1;
    // 二進位制表示 0100
    public static final int TOP               = LEFT << 2;
    // 二進位制表示 1000
    public static final int BOTTOM            = LEFT << 3;
    // 水平居中, 二進位制表示 0011
    public static final int HORIZONTAL_CENTER = LEFT | RIGHT;
    // 垂直居中, 二進位制表示 1100
    public static final int VERTICAL_CENTER   = TOP | BOTTOM;
    // 居中, 二進位制表示 1111
    public static final int CENTER            = HORIZONTAL_CENTER | VERTICAL_CENTER;
    // 預設左上角, 二進位制表示 0101
    public static final int DEFAULT           = LEFT | TOP;

    // 存放標誌位
    private int mFlags = DEFAULT;

    // 設定標記位,會清除原來的標記
    public void setFlags(int flags) {
        mFlags = flags;
    }

    // 新增標記位,在原來的基礎上新增
    public void addFlags(int flags) {
        mFlags |= flags;
    }

    // 清除指定的標記
    public void clearFlags(int flags) {
        mFlags &= ~flags;
    }

    // 清除所有標記,設為預設
    public void clears() {
        mFlags = DEFAULT;
    }

    // 判斷是否存在指定的標記
    public boolean hasFlags(int flags) {
        return (mFlags & flags) == flags;
    }

    // 判斷是否 只有指定的標記
    public boolean onlyFlags(int flags) {
        return mFlags == flags;
    }

    public void apply() {
        String des = "左上角";
        if (hasFlags(CENTER)) {
            des = "整體居中";
        } else if (hasFlags(HORIZONTAL_CENTER)) {
            if (hasFlags(BOTTOM)) des = "水平居中,豎直向下";
            else des = "水平居中,豎直向下";
        } else if (hasFlags(VERTICAL_CENTER)) {
            if (hasFlags(RIGHT)) des = "豎直居中,水平向右";
            else des = "豎直居中,水平向左";
        } else if (hasFlags(LEFT | BOTTOM)) {
            des = "左下角";
        } else if (hasFlags(RIGHT | TOP)) {
            des = "右上角";
        } else if (hasFlags(RIGHT | BOTTOM)) {
            des = "右下角";
        }
        System.out.println("你選擇的佈局是 : " + des);
    }
}

具體的呼叫實現 :


public class Main {
    public static void main(String[] args) {
        Gravity gravity = new Gravity();

        // 設定為 右下角
        gravity.setFlags(Gravity.BOTTOM | Gravity.RIGHT);
        gravity.apply();

        // 新增 left後,變為 水平居中,豎直向下
        gravity.addFlags(Gravity.LEFT);
        gravity.apply();

        // 判斷是否 水平居中, 返回為 true
        gravity.hasFlags(Gravity.HORIZONTAL_CENTER);

        // 新增top後,變為 整體居中了
        gravity.addFlags(Gravity.TOP);
        gravity.apply();

        // 刪掉 bottom和left 之後,變為右上角了
        gravity.clearFlags(Gravity.BOTTOM | Gravity.LEFT);
        gravity.apply();
    }
}
// 結果
你選擇的佈局是 : 右下角
你選擇的佈局是 : 水平居中,豎直向下
你選擇的佈局是 : 整體居中
你選擇的佈局是 : 右上角


相關文章