聊一聊 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; intCENTER_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 (a b),由於
運算滿足交換律,b
(a b)=b b^a。由於一個數和自己異或的結果為0並且任何數與0異或都會不變的,所以此時b被賦上了a的值。 - 第三步 a^=b 就是a=a
b,由於前面二步可知a=(a
b),b=a,所以a=a b即a=(a b)^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; }
小結
- " | " 運算子用來組合值
- " &" 運算子用來或者值的某個狀態或者去除值的某一個狀態
- 與非剔除值
- 非用來取反或者取絕對值
其實位操作符還有很多妙用,由於篇幅有限,這裡不再一一展開描述,下一篇,準備講解常見的位操作演算法題,敬請期待。
賣一下廣告,歡迎大家關注我的微信公眾號,掃一掃下方二維碼或搜尋微訊號 stormjun,即可關注。 目前專注於 Android 開發,主要分享 Android開發相關知識和一些相關的優秀文章,包括個人總結,職場經驗等。

image