java位運算基本原理與實際運用
在讀jdk原始碼時,經常會遇到形如這樣的程式碼:
或者是這樣:public static int numberOfLeadingZeros(int i) { // HD, Figure 5-6 if (i == 0) return 32; int n = 1; if (i >>> 16 == 0) { n += 16; i <<= 16; } if (i >>> 24 == 0) { n += 8; i <<= 8; } if (i >>> 28 == 0) { n += 4; i <<= 4; } if (i >>> 30 == 0) { n += 2; i <<= 2; } n -= i >>> 31; return n; }
if ((s & (s-1)) != 0)
由於上學時候對位操作認識不深,工作後運用也偏少,逐也漸漸淡忘,以致看到這般程式碼只能忽略而過,今天除錯一個使用fork/join模型開發的工具時,再次遇到位運算,於是就上網找了很多資料,將位運算的基本原理和常見的應用場景整理下來。
一、關於二進位制原碼反碼補碼的基礎知識
介紹位操作之前,先介紹原碼、反碼、補碼相關知識。我們都知道,計算機底層運算都是以二進位制的方式實現的,而我們平時生活計數都是用十進位制,相應我們在使用高階語言程式設計中也都是使用十進位制,那麼我們理所當然認為,編譯器把十進位制轉位二進位制計算不就可以了嗎,我們也這樣假設。
我們都知道,java中int佔4個字元,佔4*8=32個位元組,由於整數有正負則第一個位元組存符號位,那麼最大值就是2的31次方-1,那麼二進位制相加很簡單:
1+1=2 二進位制為: 0001(1)+0001(1)=0010(2) (為了方便閱讀,我們只用4個位元組,相當於byte)
那麼問題來了,因為計算機只有加法操作,沒有減法操作,所以減法操作是要轉化為加法操作的,那麼我們再看下面的程式碼:
1-1=1+(-1)=0 二進位制:0001(1)-0001(1)=0001(1)+1001(-1)=1010(-2) 很顯然是不對的。那麼就需要制定一種規則,對需要參與運算的變數應用這種規則,可以實現對減法轉為加法計算,結果為我們期望的值:假設規則為fit(),則需要滿足如下條件:
fit(1)+fit(1)=fit(2)、fit(1)-fit(1)=fit(1)+fit(-1)=fit(0) 轉為二進位制則為:fit(0001)+fit(0001)=fit(0010)、fit(0001)-fit(0001)=fit(0001)+fit(1001)=fit(0000)
經過這幫人的琢磨,於是他們定了這麼一個規則:【計算補碼:負數的補碼就是對反碼加一,而正數不變,正數的原碼反碼補碼是一樣的。】
在補碼中用(-128)代替了(-0),所以補碼的表示範圍為:(-128~0~127)共256個。
補碼的計算很簡單,在反碼的基礎上加一,比如1001求反碼為1110(符號位不變,其他都取反),加一得1111。如此1-1的運算變為這樣:
0001-10001=0001(正數補碼與原碼一樣)+1111(負數補碼)= 0000這就對了。
其實我們不難發現,負數的原碼即為:正數的原碼取反,這就是這就是補碼的作用,這麼一來,負數在計算機中的表示法就成了補碼了。
二、位移運算
(1) << 左移
(2) >> 右移
(3) >>> 無符號右移
注意:位移操作只針對整型資料有效,包括int,byte,short,char,long,處int外,其他四種類型JVM會先把它們轉換成int型再進行操作。
m<<n的含義:把整數m表示的二進位制數左移n位,高位移出n位都捨棄,低位補0.
(此時將會出現正數變成負數的形式)
例項(為了便於閱讀,我們只用8個位元組):
1)、 m<<n的含義:把整數m表示的二進位制數左移n位,高位移出n位都捨棄,低位補0.
(此時將會出現正數變成負數的形式)
3<<2剖析:
3 的二進位制位00000011,左移兩位後得到00001100,即為12. (12=3*2^2)
注意:左移可能使整數變為負數,即如果左移後最高位為1
2)、m>>n
的含義:把整數m表示的二進位制數右移n位,m為正數,高位全部補0;m為負數,高位全部補1.
例項:
3>>2剖析:
3二進位制形式: 00000011,右移2位得到00000000,即為0.
-3>>2剖析:
-3二進位制形式: 11111101,右移2位,得到11111111,即為-1.(要注意了,這裡看到的可是補碼哈。)
以上:每個整數表示的二進位制都是32位的,如果右移32位和右移0位的效果是一樣的。依次類推,右移32的倍數位都一樣。
3)、m>>>n:整數m表示的二進位制右移n位,不論正負數,高位都補零。
例項:
3>>>2剖析:
3二進位制形式: 00000011,高位補零右移2位,得到00000000,即為0.
-3>>>2剖析:
-3二進位制形式: 11111101,右移,得到00111111
【注】:對於以上三種操作,如果n為負數:這時JVM會先讓n對32取模,變成一個絕對值小於32的負數,然後再加上32,直到 n 變成一個正數。
三、按位操作
java中的位操作,包括四個操作符: (1)~(按位非) (2)|(按位或) (3)&(按位與) (4)^(按位異或) 編碼中對十進位制整型變數進行按位操作後,先對整型的二進位制形式進行操作,然後再轉換為整數,具體操作如下。
1).~(按位非):對該整數的二進位制形式逐位取反。
~4:(一元操作符)
4的二進位制形式為:00000100,逐位取反後得到:11111011,即為-5.
2).|(按位或):對兩個整數的二進位制形式逐位進行邏輯或運算,原理為:1|0=1,0|0=0,1|1=1,0|1=1
等。
4|-5:
4的二進位制形式為:00000100,
-5的二進位制形式為:11111011,
逐位進行邏輯或運算:11111111,即得到-1.
3).&(按位與):對兩個整數的二進位制形式逐位進行邏輯與運算,原理:1|0=0,0|0=0,1&1=1;0&1=0等。
4&-5:
4的二進位制形式為:00000100,
-5的二進位制形式為:11111011,
逐位進行邏輯與運算:00000000,即得到0.
4).^(按位異或):【解義】對兩個整數的二進位制形式逐位進行邏輯異或運算,原理:1^1=0,1^0=1,0^1=1,0^0=0.
4^-5:
4的二進位制形式為:00000100,
-5的二進位制形式為:11111011,
逐位進行邏輯異或運算:11111111,即得到-1.
四、實際運用
1、m<<n即在數字沒有溢位的前提下,對於正數和負數,左移n位都相當於m乘以2的n次方, m>>n即相當於m除以2的n次方,得到的為整數時,即為結果。如果結果為小數,此時會出現兩種情況:(1)如果m為正數,得到的商會無條件的捨棄小數位;(2)如果m為負數,捨棄小數部分,然後把整數部分加+1得到位移後的值。 比如取半數就可以用n>>1
2、按位異或可以比較兩個數字是否相等,它利用1^1=0,0^0=0的原理。 20^20==0
3、 判斷int型變數a是奇數還是偶數
a&1 = 0 偶數
a&1 = 1 奇數
4、 求平均值,比如有兩個int型別變數x、y,首先要求x+y的和,再除以2,但是有可能x+y的結果會超過int的最大表示範圍,所以位運算就派上用場啦。
(x&y)+((x^y)>>1);
5、 對於一個大於0的整數,判斷它是不是2的幾次方
((x&(x-1))==0)&&(x!=0);
6、 比如有兩個int型別變數x、y,要求兩者數字交換,位運算的實現方法:效能絕對高效
x ^= y;
y ^= x;
x ^= y;
7、 取模運算,採用位運算實現:
a % (2^n) 等價於 a & (2^n - 1)
8、 a % 2 等價於 a & 1
public class NewPermission {
// 是否允許查詢,二進位制第1位,0表示否,1表示是
public static final int ALLOW_SELECT = 1 << 0; // 0001
// 是否允許新增,二進位制第2位,0表示否,1表示是
public static final int ALLOW_INSERT = 1 << 1; // 0010
// 是否允許修改,二進位制第3位,0表示否,1表示是
public static final int ALLOW_UPDATE = 1 << 2; // 0100
// 是否允許刪除,二進位制第4位,0表示否,1表示是
public static final int ALLOW_DELETE = 1 << 3; // 1000
// 儲存目前的許可權狀態
private int flag;
/**
* 重新設定許可權
*/
public void setPermission(int permission) {
flag = permission;
}
/**
* 新增一項或多項許可權
*/
public void enable(int permission) {
flag |= permission;
}
/**
* 刪除一項或多項許可權
*/
public void disable(int permission) {
flag &= ~permission;
}
/**
* 是否擁某些許可權
*/
public boolean isAllow(int permission) {
return (flag & permission) == permission;
}
/**
* 是否禁用了某些許可權
*/
public boolean isNotAllow(int permission) {
return (flag & permission) == 0;
}
/**
* 是否僅僅擁有某些許可權
*/
public boolean isOnlyAllow(int permission) {
return flag == permission;
}
}
詳情看http://www.tuicool.com/articles/Zr6R3q
10、在影象處理方面使用比較多,比如處理顏色時,ARGB資料是一個int,
int color
可以用
((color & 0xFF000000)>>24) & 0xFF //得到A的值 ,也就是透明通道的值
((color & 0x00FF0000)>>16) & 0xFF //R 也就是紅色的值
((color & 0x0000FF00)>>8 ) & 0xFF //G 綠色的值
((color & 0x000000FF) ) & 0xFF //B 藍色的值
返過來也可以通過 A R G B的值來組成一個ARGB資料來進行處理。
當你要處理圖顏色時 你就發現,位移非常方便。
11、大小寫轉換:小寫轉大寫:(char)('a'&~32),大寫轉小寫:(char)('A'|32),當然我們一般還是用jdk的原生庫,這裡只做介紹。