在平常的Java開發中,位運算使用的很少,使用的更多的是算數運算(+、-、*、/、%)、關係運算(<、>、<=、>=、==、!=)和邏輯運算(&&、||、!),因此相對來講對位運算不是那麼熟悉,本文將以Java的位運算來詳細介紹下位運算及其應用。算法
一、 位運算起源架構
位運算起源於C語言的低級操做,Java的設計初衷是嵌入到電視機頂盒內,因此這種低級操做方式被保留下來。所謂的低級操做,是由於位運算的操做對象是二進制位,可是這種低級操做對計算機而言是很是簡單直接,友好高效的。在簡單的低成本處理器上,一般位運算比除法快得多,比乘法快幾倍,有時比加法快得多。雖然因爲較長的指令流水線和其餘架構設計選擇,現代處理器一般執行加法和乘法的速度與位運算同樣快,但因爲資源使用減小,位運算一般會使用較少的功率,因此在一些Java底層算法中,巧妙的使用位運算能夠大量減小運行開銷。spa
二、 位運算詳解架構設計
Java位運算細化劃分能夠分爲按位運算和移位運算,見下表。設計
細化對象 |
符號ci |
描述資源 |
運算規則開發 |
按位運算get |
& |
與 |
兩位都爲1,那麼結果爲1 |
| |
或 |
有一位爲1,那麼結果爲1 |
|
~ |
非 |
~0 = 1,~1 = 0 |
|
^ |
異或 |
兩位不相同,結果爲1 |
|
移位運算 |
<< |
左移 |
各二進制位所有左移N位,高位丟棄,低位補0 |
>> |
右移 |
各二進制位所有右移N位,若值爲正,則在高位插入 0,若值爲負,則在高位插入 1 |
|
>>> |
無符號右移 |
各二進制位所有右移N位,不管正負,都在高位插入0 |
在進行位運算詳解以前,先來普及下計算機中數字的表示方法。對於計算機而言,萬物皆0、1,全部的數字最終都會轉換成0、1的表示,有3種體現形式,分別是:原碼、反碼和補碼。
原碼:原碼錶示法在數字前面增長了一位符號位,即最高位爲符號位,正數位該位爲0,負數位該位爲1.好比十進制的5若是用8個二進制位來表示就是00000101,-5就是10000101。
反碼:正數的反碼是其自己,負數的反碼在其原碼的基礎上,符號位不變,其他各個位取反。5的反碼就是00000101,而-5的則爲11111010。
補碼:正數的補碼是其自己,負數的補碼在其原碼的基礎上,符號位不變,其他各位取反,最後+1。即在反碼的基礎上+1。5的反碼就是00000101,而-5的則爲11111011。
瞭解了這幾個概念後,咱們如今先記住一個結論,那就是在計算機系統中,數字一概用補碼來表示、運算和存儲,具體的緣由能夠看這篇文章的討論,這裏不作更多討論,由於不是本文的重點。
2.1 與運算(&)
規則:轉爲二進制後,兩位爲1,則結果爲1,不然結果爲0。
舉例:
十進制 |
二進制(正數原碼、反碼、補碼一致) |
10 |
00000000000000000000000000001010 |
&12 |
&00000000000000000000000000001100 |
= |
= |
8 |
00000000000000000000000000001000 |
十進制 |
二進制(原碼) |
-6 |
10000000000000000000000000000110 |
&-2 |
&10000000000000000000000000000010 |
十進制 |
二進制(反碼) |
-6 |
11111111111111111111111111111001 |
&-2 |
&11111111111111111111111111111101 |
十進制 |
二進制(補碼) |
-6 |
11111111111111111111111111111010 |
&-2 |
&11111111111111111111111111111110 |
= |
= |
-6 |
11111111111111111111111111111010 |
最後的計算結果11111111111111111111111111111010仍是補碼的形式,要看其十進制,還須要先轉成二進制原碼。
先轉反碼:11111111111111111111111111111010-1=11111111111111111111111111111001,得反碼11111111111111111111111111111001。
再轉原碼:在反碼的基礎上轉原碼,符號位不變,其餘各位取反,得10000000000000000000000000000110。第一位1表明負數,後面0110轉成十進制是6,得-6。
2.2 或運算(|)
規則:轉爲二進制後,有一位爲1,則結果爲1,不然結果爲0。
舉例:
十進制 |
二進制(正數原碼、反碼、補碼一致) |
10 |
00000000000000000000000000001010 |
|12 |
|00000000000000000000000000001100 |
= |
= |
14 |
00000000000000000000000000001110 |
十進制 |
二進制(原碼) |
-6 |
10000000000000000000000000000110 |
|-2 |
|10000000000000000000000000000010 |
十進制 |
二進制(反碼) |
-6 |
11111111111111111111111111111001 |
|-2 |
|11111111111111111111111111111101 |
十進制 |
二進制(補碼) |
-6 |
11111111111111111111111111111010 |
|-2 |
|11111111111111111111111111111110 |
= |
= |
-2 |
11111111111111111111111111111110 |
2.3 非運算(~)
規則:轉爲二進制後,~0 = 1,~1 = 0。
舉例:
十進制 |
二進制(正數原碼、反碼、補碼一致) |
~7 |
~00000000000000000000000000000111 |
= |
= |
-8 |
11111111111111111111111111111000(補碼需轉換爲原碼) |
11111111111111111111111111111000-1得反碼,能夠把1000當作是0112,得反碼11111111111111111111111111110111。根據反碼得原碼10000000000000000000000000001000。
十進制 |
二進制(原碼) |
~(-6) |
~10000000000000000000000000000110 |
十進制 |
二進制(反碼) |
~(-6) |
~11111111111111111111111111111001 |
十進制 |
二進制(補碼) |
~(-6) |
~11111111111111111111111111111010 |
= |
= |
5 |
00000000000000000000000000000101(正數原碼、反碼、補碼一致) |
2.4 異或運算(^)
規則:轉爲二進制後,兩位不相同,結果爲1,不然爲0。
舉例:
十進制 |
二進制(正數原碼、反碼、補碼一致) |
15^2 |
00000000000000000000000000001111 ^00000000000000000000000000000010 |
= |
= |
13 |
00000000000000000000000000001101 |
2.5 左移運算(<<)
規則:轉爲二進制後,各二進制位所有左移N位,高位丟棄,低位補0。
舉例:
十進制 |
二進制(正數原碼、反碼、補碼一致) |
2<<2 |
00000000000000000000000000000010 |
= |
0000000000000000000000000000001000 |
8 |
00000000000000000000000000001000 |
十進制 |
二進制(先取補碼 再對補碼操做位移) |
-2<<2 |
10000000000000000000000000000010(原碼) |
|
11111111111111111111111111111101(反碼) |
|
11111111111111111111111111111110(補碼) |
|
1111111111111111111111111111111000 |
|
11111111111111111111111111111000(補碼) |
|
11111111111111111111111111110111(反碼) |
-8 |
10000000000000000000000000001000(原碼) |
2.6 右移運算(>>)
規則:轉爲二進制後,各二進制位所有右移N位,若值爲正,則在高位插入 0,若值爲負,則在高位插入 1。
舉例:
十進制 |
二進制(正數原碼、反碼、補碼一致) |
2>>2 |
00000000000000000000000000000010 |
= |
0000000000000000000000000000000010 |
0 |
00000000000000000000000000000000 |
十進制 |
二進制(先取補碼 再對補碼操做位移) |
-6>>2 |
10000000000000000000000000000110(原碼) |
|
11111111111111111111111111111001(反碼) |
|
11111111111111111111111111111010(補碼) |
|
1111111111111111111111111111111010 |
|
11111111111111111111111111111110(補碼) |
|
11111111111111111111111111111101(反碼) |
-2 |
10000000000000000000000000000010(原碼) |
2.7 無符號右移運算(>>>)
規則:轉爲二進制後,各二進制位所有右移N位,不管正負,都在高位插入0。
舉例
十進制 |
二進制(先取補碼 再對補碼操做位移) |
-1>>>1 |
10000000000000000000000000000001(原碼) |
|
11111111111111111111111111111110(反碼) |
|
11111111111111111111111111111111(補碼) |
|
011111111111111111111111111111111 |
|
01111111111111111111111111111111(補碼) |
|
01111111111111111111111111111110(反碼) |
溢出,只能表示到int的最大值2147483647 |
10000000000000000000000000000001(原碼) |
三、 應用
3.1 不用額外的變量實現兩個數字互換
見參考資料中的BitOperationTest,方法reverse經過三次異或操做完成了兩個變量值的替換。
證實很簡單,咱們只須要明白異或運算知足下面規律(實際不止以下規律):
0^a = a,a^a = 0;
a ^ b = b ^ a;
a ^ b ^ c = a ^ (b ^ c) = (a ^ b) ^ c;
a ^ b ^ a = b;
假設a,b兩個變量,通過以下步驟完成值交換:a=a^b,b=b^a,a=a^b。
證實以下:
由於a ^ b = b ^ a,又a=a^b,b=b^a。故b=b^a= b^ (a^b)=a。
繼續a=a^b,a=(a^b) ^ b^ (a^b),故a=b。完成值交換。
3.2 不用判斷語句實現求絕對值
公式以下:(a^(a>>31))-(a>>31)
先整理一下使用位運算取絕對值的思路:若a爲正數,則不變,須要用異或0保持的特色;若a爲負數,則其補碼爲原碼翻轉每一位後+1,先求其原碼,補碼-1後再翻轉每一位,此時須要使用異或1具備翻轉的特色。
任何正數右移31後只剩符號位0,最終結果爲0,任何負數右移31後也只剩符號位1,溢出的31位截斷,空出的31位補符號位1,最終結果爲-1.右移31操做能夠取得任何整數的符號位。
那麼綜合上面的步驟,可獲得公式。a>>31取得a的符號,若a爲正數,a>>31等於0,a^0=a,不變;若a爲負數,a>>31等於-1 ,a^-1翻轉每一位。
3.3 判斷一個數的奇偶性
經過與運算判斷奇偶數,僞代碼以下:
n&1 == 1?」奇數」:」偶數」
奇數最低位確定是1,而1的二進制最低位也是1,其餘位都是0,因此全部奇數和1與運算結果確定是1。