1. 程式人生 > >聊一聊 Android 中巧妙的位操作

聊一聊 Android 中巧妙的位操作

前言

我們之前,在計算機當中,它是以二進位制的形式來進行數的儲存和加減乘除的。

講解之前,我們先來了解一下基本的位操作

位操作 含義 具體含義
& 表示與 兩位同時為 1,結果才為 1,否則為 0
"| " 表示或 兩位中只要有一個為 1,結果為 1
^ 表示異或 兩位中數字不相同為 1,否則為 0
表示取法 為單目運算子,表示取反
<< 左移運算子 向左移動一位
>> 右移運算子 向右移動一位

與運算子 &

兩位同時為“1”,結果才為“1”,否則為“0”。

0 & 0 = 0; 0 & 1 = 0; 1 & 0 = 0; 1 & 1 = 1

或運算子 |

兩位中只要有一位為 1,結果就為 1

0 | 0 = 0; 0 | 1= 1; 1 | 0 = 1; 1 | 1 = 1

異或雲算符 ^

兩位中只要數字不相同,結果即為 1

0 ^ 0 = 0;1 ^ 0= 1;0 ^ 1 = 1; 1 ^ 1 = 0

取反運算子 ~

左移運算:

左移運算 左移n位的時候,最左邊的n位將被丟棄,同時在最右邊補上n個0.比如:

右移運算:

對於 >> 運算子來說,右移:(正數左補0、負數左補1)

System.out.println(-3>>1);

結果是 -2 ,為什麼會是 -2 呢?下面我們來看一下

轉換成2進製為1111 1111 1111 1111 1111 1111 1111 1101

右移一位為1111 1111 1111 1111 1111 1111 1111 1110,顯而易見此為-2補碼.

對於 >>> 運算子

無符號右移(正數左補0負數左補1)

System.out.println(-3>>>1);

結果為 2147483646

1111 1111 1111 1111 1111 1111 1111 1101無符號右移,高位補0,

01111 1111 1111 1111 1111 1111 1111 1110,其為2147483646的原碼.

Android 中位運算子的應用

“|” 或運算子的應用

或運算子可以用來組合多種值。

舉個例子,在 Android 中,我們經常會看到這樣的寫法

TextView tv = new TextView(context);
tv.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT);

即 TextView 垂直居中並且向左對齊。

如果不採用或運算子來寫,採用布林值來記錄每一種狀態,那每一次繪製 TextView 的時候,你得判斷多少次,才能得出 TextView 的對其方向。

因為 TextView 的對齊方向有可能 是左上,左下,左中,右上,右下,右中,中上,中下,垂直居中 ----。

採用或運算子來組合多種值的時候,為了便於獲取原來的狀態,這裡我們需要注意一下,採用位上錯開的原則:

什麼叫位上錯開?

舉個例子,假設 LEFT 的值為 0x0001,即第一位為1,CENTER_VERTICAL 的值為 0x0002 ,即第二位為 1,與 LEFT 第二位的值不同,那這樣就叫位上錯開。

into LEFT =  0x0001; 
int  CENTER_VERTICAL = = 0x0002

位上錯開有什麼好處呢?

  • 節省空間,避免不必要的屬性出現和維護成本(難道你想一個狀態用一個布林值來維護麼?)
  • 獲取方便,編碼簡潔,位運算也更加高效

“&” 與運算子

判斷是否含有某種狀態

上面我們說到或運算子可以用來組合多種值,那我們如何判斷組合後的值含有某種狀態,其實很簡單。

跟原來的某一狀態進行與,若值與該狀態相等,證明含有該狀態

int gravity = tv.getGravity();
if ((gravity & Gravity.LEFT) == Gravity.LEFT) {

}

判斷是否是奇數或者偶數

  • 只需判斷最後一位是1還是0
  • 最後一位是1,說明是奇數。最後一位是0,說明是偶數
  • 因為只有2的0次方才是奇數值1,其他的2的k(k = 1,2,….)都是偶數
public boolean isOdd(int num) {
    return (num & 1) != 0;
}

異或運算子的應用

異或運算子,只要兩位不相等,結果為 1, 否則為 0.

因此,我們容易得出這一樣的結果

  • 任何數和自己異或結果為零。
  • 任何數和0做異或值不變

使用異或運算子實現值的交換

void Swap(int a, int b)  
{  
    if (a != b)  
    {  
        a ^= b;  
        b ^= a;  
        a ^= b;  
    }  
}  
  • 第一步 a^=b 即a=(a^b);
  • 第二步 b^=a 即b=b(ab),由於運算滿足交換律,b(ab)=bb^a。由於一個數和自己異或的結果為0並且任何數與0異或都會不變的,所以此時b被賦上了a的值。
  • 第三步 a^=b 就是a=ab,由於前面二步可知a=(ab),b=a,所以a=ab即a=(ab)^a。故a會被賦上b的值。

再來個例項說明下以加深印象。int a = 13, b = 6;

a的二進位制為 13=8+4+1=1101(二進位制)

b的二進位制為 6=4+2=110(二進位制)

  • 第一步 a^=b a = 1101 ^ 110 = 1011;
  • 第二步 b^=a b = 110 ^ 1011 = 1101;即b=13
  • 第三步 a^=b a = 1011 ^ 1101 = 110;即a=6

非運算子的運用

非運算子的作用就是按位取反。

值裡面去除某個狀態

或許,你會有這樣的一個疑問,如果我想剔除當前已經包含的一個值,需要怎麼辦?這時候就是“非”和“與”運算子聯合使用的時候了,且看下面程式碼

int left = 0x001;
int right = 0x002;
int top = 0x008;
int state = left | right;
System.out.println("剔除 right 狀態前 " + state);
state &= ~right;
System.out.println("剔除 right 狀態後 " + state);
state &= ~top;
System.out.println("剔除不存在的 top 狀態 " + state);

輸出 log 如下

剔除 right 狀態前 3
剔除 right 狀態後 1
剔除不存在的 top 狀態 1

變換符號

如對於-11和11,可以通過下面的變換方法將-11變成11

      1111 0101(二進位制) –取反-> 0000 1010(二進位制) –加1-> 0000 1011(二進位制)

同樣可以這樣的將11變成-11

      0000 1011(二進位制) –取反-> 0000 0100(二進位制) –加1-> 1111 0101(二進位制)
int SignReversal(int a)  
{  
    return ~a + 1;  
}  

取絕對值

int my_abs(int a)  
{  
    int i = a >> 31;  
    return i == 0 ? a : (~a + 1);  
}  

現在再分析下。對於任何數,與0異或都會保持不變,與-1即0xFFFFFFFF異或就相當於取反。因此,a與i異或後再減i(因為i為0或-1,所以減i即是要麼加0要麼加1)也可以得到絕對值。所以可以對上面程式碼優化下:

int my_abs(int a)  
{  
    int i = a >> 31;  
    return ((a ^ i) - i);  
}  

左移與右移運算子的應用

用來判斷某一位是 1

/**
 * 判斷第 n 位是 否為 1,注意 n 從 0 開始
 * @param num
 * @param n
 * @return
 */
public static boolean nBitisOne(long num, int n) {
    if (num <= 0) {
        return false;
    }
    return (1 & (num >> n)) == 1;
}


小結

  1. " | " 運算子用來組合值
  2. " &" 運算子用來或者值的某個狀態或者去除值的某一個狀態
  3. 與非剔除值
  4. 非用來取反或者取絕對值

其實位操作符還有很多妙用,由於篇幅有限,這裡不再一一展開描述,下一篇,準備講解常見的位操作演算法題,敬請期待。

賣一下廣告,歡迎大家關注我的微信公眾號,掃一掃下方二維碼或搜尋微訊號 stormjun,即可關注。 目前專注於 Android 開發,主要分享 Android開發相關知識和一些相關的優秀文章,包括個人總結,職場經驗等。