在日常開發中,其實位操作、移位操作的使用並不多,主要是其可讀性較差,但是在計算密集型操作如一致性哈希計算、hashmap擴容、取數據的交集、差集、並集、權限開關位,位操作、移位操作被廣泛使用。因此本文章來介紹Java中的位操作、移位操作,當然LZ特別說明我們的使用場景。
一、 相關基礎概念
在開始java位運算的知識之前,我們先來了解幾個基礎的概念,機器數,真值,原碼,反碼,補碼。
1.機器數
我們知道無論是代碼還是數值,在計算機中最后都轉換成以二進制的形式存在的,而一個數值在計算機中的二進制表示形式,就是這個數的機器數。機器數是有符號位的,在計算機中用一個二進制數的最高位存放符號,正數為0,負數為1,如下實例(按原碼表示):
十進制的+5,計算機字長為8位,其二進制就是00000101
十進制的 -5,計算機字長為8位,其二進制就是10000101(這里用的是原碼)
其中00000101和10000101就是機器數
2.真值
由於機器數的第一位是符號位,所以其形式值就不等於其真值的數值,也就是說10000101表示的是-5而不是133(10000101的十進制是131,前提是不算最高位為符號位),因此-5才是機器數的真值。
3.原碼
原碼是一種計算機中對數字的二進制定點表示方法。原碼表示法在數值前面增加了一位符號位(即最高位為符號位):正數該位為0,負數該位為1,其余位表示數值的大小。
[+5]=[00000101](原碼)
[ - 5]=[10000101](原碼)
因為第一位是符號位,因此8位二進制的取值范圍就是[1111 1111,0111 1111]也就是[-127,127]
4.反碼
反碼是數值存儲的一種,但是由於補碼更能有效表現數字在計算機中的形式,所以多數計算機一般都不采用反碼表示數,反碼的表示方法如下:
正數的反碼是其本身
負數的反碼是在其原碼的基礎上, 符號位不變,其余各個位取反.
[+5]=[00000101](原碼)= [00000101](反碼)
[ - 5]=[10000101](原碼)= [11111010](反碼)
5.補碼
在計算機系統中,數值一律用補碼來表示和存儲。原因在於,使用補碼,可以將符號位和數值域統一處理;同時,加法和減法也可以統一處理。補碼的表示方法是:
正數的補碼就是其本身
負數的補碼是在其原碼的基礎上, 符號位不變, 其余各位取反, 最后+1. (即在反碼的基礎上+1)
[+5]=[00000101](原碼)= [00000101](反碼)=[00000101](補碼)
[ - 5]=[10000101](原碼)= [11111010](反碼)=[11111011](補碼)
6.小結
計算機中的符號數有三種表示方法,即原碼、反碼和補碼。三種表示方法均有符號位和數值位兩部分,符號位都是用0表示“正”,用1表示“負”,而數值位,三種表示方法各不相同。而在計算機系統中,數值一律用補碼來表示和存儲。
二、 Java位運算
位移操作:(只針對 int類型的數據有效,java中,一個int的長度始終是32位,也就是4個字節,它操作的都是該整數的二進制數).也可作用於以下類型,即 byte,short,char,long(它們都是整數形式)。當為這四種類型時,JVM先把它們轉換成int型再進行操作。
private int x=0b00000101; //數字5的二進制表示
7.左移(<<)
m<<n的含義:把整數m表示的二進制數左移n位,高位移出n位都舍棄,低位補0. (此時將會出現正數變成負數的可能),如下實例:
5<<2 :把十進制的數值5左移兩位,按如下步驟計算,
把5轉位16位的二進制機器數:00000000 00000000 00000000 00000101
按左移原理,將二進制數左移兩位:00000000 00000000 00000000 00010100
左移后結果為20
5<<29:把十進制的數值5左移29位,按如下步驟計算,
把5轉位16位的二進制機器數:00000000 00000000 00000000 00000101
按左移原理,將二進制數左移29位:10100000 00000000 00000000 00000000
左移后高位是1,結果顯然是負數
小結:m<<n即在數字沒有溢出的前提下,對於正數和負數,左移n位都相當於m乘以2的n次方.
8.右移(>>)
m>>n的含義:把整數m表示的二進制數右移n位,m為正數,高位全部補0;m為負數,高位全部補1,實例如下:
5>>2 :把十進制的數值5右移兩位,按如下步驟計算,
把5轉位16位的二進制機器數:00000000 00000000 00000000 00000101
按右移原理,將二進制數左移兩位:00000000 00000000 00000000 00000001
右移后結果為1
-5>>2:把十進制的數值-5右移兩位,按如下步驟計算,
把-5轉位16位的二進制機器數:11111111 11111111 11111111 11111011
按右移原理,將二進制數右移兩位:11111111 11111111 11111111 11111110
右移后結果為-2
小結:
m>>n即相當於m除以2的n次方,得到的為整數時,即為結果。如果結果為小數,此時會出現兩種情況:
如果m為正數,得到的商會無條件 的舍棄小數位;
如果m為負數,舍棄小數部分,然后把整數部分加+1得到位移后的值。
9.無符號右移(>>>)
m>>>n:整數m表示的二進制右移n位,不論正負數,高位都補0,實例如下:
5>>>2 :把十進制的數值5右移兩位,按如下步驟計算,
把5轉位16位的二進制機器數:00000000 00000000 00000000 00000101
按右移原理,將二進制數左移兩位:00000000 00000000 00000000 00000001
右移后結果為1
-5>>>2:把十進制的數值-5右移兩位,按如下步驟計算,
把-5轉位16位的二進制機器數:11111111 11111111 11111111 11111011
按右移原理,將二進制數右移兩位:00111111 11111111 11111111 11111110
右移后結果為正數
10.按位非操作(~)
~ 按位取反操作符,對每個二進制位的內容求反,即1變成0,0變成1實例如下
把-5轉位16位的二進制機器數:11111111 11111111 11111111 11111011
~(-5) 取反結果:00000000 00000000 00000000 00000100
轉為十進制,結果為4
11.按位與操作(&)
& 位與操作符,對應的二進制位進行與操作,兩個都為1才為1,其他情況均為0,原理如下:
1&0=0
0&0=0
1&1=1
0&1=0
實例:-5 & 4
-5的二進制形式為: 11111111 11111111 11111111 11111011
4的二進制形式為: 00000000 00000000 00000000 00000100
——————————————————————————————
邏輯與運算結果: 00000000 00000000 00000000 00000000
最終結果為0。
12.按位或操作(|)
| 位或操作符,對應的二進制位進行或操作,兩個都為0才為0,其他情況均為1,原理如下:
1|0=1
0|0=0
1|1=1
0|1=1
實例:-5 | 4
-5的二進制形式為:11111111 11111111 11111111 11111011
4的二進制形式為:00000000 00000000 00000000 00000100
————————————————————————————
邏輯或運算結果: 11111111 11111111 11111111 11111111
最終結果為-1。
利用或的原理我們可以把字節轉換為整數,-64&0xFF=192,其中0xFF表示整數255。
13.按位異或操作( ^ )
^ 異或操作符,相同位值為0 否則為1,原理如下:
1^1=0
1^0=1
0^1=1
0^0=0
實例:-5 ^ 4
-5的二進制形式為:11111111 11111111 11111111 11111011
4的二進制形式為:00000000 00000000 00000000 00000100
————————————————————————————
邏輯異或運算結果: 11111111 11111111 11111111 11111111
最終結果為-1。
其實利用邏輯異或操作有個作用就是可以比較兩個數值是否相等,即利用1^1=0,0^0=0的原理,如5^5==0。
14.總結
通過上面的分析,我們對java的位運算也算有了比較全面的了解,那么我們的程序通過位運算又有什么優勢呢?其實通過位運算確實會比我們直接的程序代碼運算會快很多,因為位運算直接運算的是計算機底層的二進制機器操作指令,而我們的程序代碼運算最終也是要轉成計算機可識別的二進制操作指令才能執行,位運算可以理解為省了中間轉換的操作,處理器可以直接操作。
參考:https://segmentfault.com/a/1190000018485946