在位運算前,需要先了解二進制碼相關知識,詳情請見博主的另一篇博文:原碼、反碼、補碼
Java定義了位運算符,應用於整數類型(int),長整型(long),短整型(short),字符型(char),和字節類型(byte)等類型。
Java包含了七種位運算符
位運算符 | 說明 |
---|---|
>> | 右移運算符,符號左側數值 按位右移 符號右側數值指定的位數,若爲正數則高位補0,若爲負數則高位補1 |
<< | 左移運算符,符號左側數值 按位左移 符號右側數值指定的位數,並在低位處補0 |
>>> | 無符號右移運算符,符號左側數值 按位右移 符號右側數值指定的位數,無論正負高位補0 |
& | 與(AND)運算符,對兩個整型操作數中對應位執行布爾代數,兩個位都爲1時輸出1,否則0 |
| | 或(OR)運算符,對兩個整型操作數中對應位執行布爾代數,兩個位中只要有一個爲1就輸出1,否則爲0 |
^ | 異或(XOR)運算符,對兩個整型操作數中對應位執行布爾代數,兩個位相等則爲0,不相等則爲1 |
~ | 非(NOT)運算符,按位取反運算符翻轉操作數的每一位,即0變成1,1變成0 |
七種位運算示例代碼
int a = -20;
int b = 30;
int result1 = a << 1;
int result2 = a >> 1;
int result3 = a >>> 1;
int result4 = a & b;
int result5 = a | b;
int result6 = a ^ b;
int result7 = ~ a;
運算結果如下
-40
-10
2147483638
12
-2
-14
19
原理解析如下(高位0爲博主手動補齊,方便觀看)
a = 11111111 11111111 11111111 11101100
b = 00000000 00000000 00000000 00011110
-----------------
a << 1 --> 11111111 11111111 11111111 11011000
a >> 1 --> 11111111 11111111 11111111 11110110
a >>> 1 --> 01111111 11111111 11111111 11110110
a & b = 00000000 00000000 00000000 00001100
a | b = 11111111 11111111 11111111 11111110
a ^ b = 11111111 11111111 11111111 11110010
~a = 00000000 00000000 00000000 00010011
進行位操作時,除long型外,其他類型會自動轉成int型,轉換之後,可接受右操作數長度爲32。進行位運算時,總是先將短整型和字節型值轉換成整型值再進行移位操作的
數據類型 | 大小 |
---|---|
byte | 8 bit |
short | 16 bit |
char | 16 bit |
int | 32 bit |
long | 64bit |
示例
byte a = -128;
byte b = 63;
byte result16 = (byte)(a << 1);
byte result17 = (byte)(a >> 1);
byte result18 = (byte)(a >>> 1);
byte result19 = (byte)(a & b);
byte result20 = (byte)(a | b);
byte result21 = (byte)(a ^ b);
byte result22 = (byte)(~ a);
上面的代碼在位運算後類型自動提升爲了int,所以需要使用int類型的變量來接受,但是我們可以在進行位運算後進行強轉,但強轉會直接截取字節,從而導致丟失精度,最終得到的結果如下
0
-64
-64
0
-65
-65
127
對於 int 類型的整數移位 a >> b, 當 b>32 時,系統先用 b 對 32 求餘(因爲 int 是 32 位),得到的結果纔是真正移位的位數,例如,a >> 33 和 a >> 1 的結果相同,而 a >> 32 = a
知道了位運算的規則,那麼我們什麼時候可以使用到位運算?位運算的使用場景有哪些呢?
位運算的使用場景如下:
1. 判斷奇偶性
public void method1(int a){
if (a&1 == 0) {
log.info("偶數")
}else if ( a&1 == 1) {
log.info("奇數")
}
}
偶數的最低位肯定是0,奇數的最低位肯定是1,而1的最低位是1其他位都爲零,當進行與運算時:
- 偶數必然:a&1 == 0
- 奇數必然:a&1 == 1
2. 不使用中間變量交換兩個數
public void swap(int a , int b) {
a = a ^ b;
b = b ^ a;
a = a ^ b;
}
這裏需要知道兩點:
- 任何數和自己進行異或操作結果都爲0
- 異或符合交換律,即a ^ b = b ^ a
好的,那麼上面代碼操作就等於:
a = a ^ b;
b = b ^ a = b ^ (a ^ b) = a;
a = a ^ b = (a ^ b) ^ (b ^ (a ^ b)) = (a ^ b) ^ a = b;
3. 判斷一個正整數是不是2的整數次冪
public boolean power2(int a) {
if (a <= 0){
System.out.println("這裏不計算負數,直接返回false");
return false;
} else{
return (a&(a-1))==0
}
}
任何正整數如果是2的冪數,都形如下
10
100
1000
10...0
即首位都爲1,往後位數都爲0,那麼在減去1後又都形如下
01
011
0111
01...1
所以大於零的2的冪數和自己減一後的數進行與運算結果必然爲0
4. 計算整數的平均值
public int average(int a, int b){
return (a&b)+((a^b)>>1);
}
具體原理詳見博主的另一篇博文:位運算求整數的平均值
5. 計算絕對值
public int abs( int a ) {
return (a + (a >> 31)) ^ (a >> 31) ;
}
體原理詳見博主的另一篇博文:位運算求整數的絕對值
6. 取模運算轉化成位運算 (在不產生溢出的情況下)
注意,這裏只支持正數的取模操作
a % (2^n) = a & (2^n - 1)
取模得到的值肯定小於模數,即
11%2 那麼得到的值肯定小於2,也就是[0-2)之間, 00000000 - 00000001
101%8 那麼得到的值肯定小於8,也就是[0,8)之間, 00000000 - 00000111
---------------------
現在我們再來看一下具體的模操作
11%2=1
00001011
%
00000010
得到
00000001
101%8=5
01100101
%
00001000
得到
00000101
---------------------
從上面的例子我們可以看到一個現象,一個數對2的冪數的模,其實就是被模數按照模數的位數的截取
即
如果模數是8
1000
那麼不管被模數是多少,它的模都爲被模數後三位所代表的值
如果被模數爲20
10010
那麼它的模就是010,也就是2
---------------------
當然,有一種另類情況,就是被模數小於模數,那麼它們的模就等於被模數本身
例如:2%8=2
那麼通過什麼方式可以獲取被模數的需要截取的位數呢?正好,與操作就可以做到,那爲什麼需要-1呢?
因爲2的冪數-1永遠都是形如下面的格式
1
11
111
1...1
真好是這個特性保證了a & (b-1)能夠獲得後幾位的值,所以
a % b = a & (b-1)
7. 乘法運算轉化成位運算 (在不產生溢出的情況下)
a * (2^n) = a < < n
8. 除法運算轉化成位運算 (在不產生溢出的情況下)
a / (2^n) = a >> n
9.對稱加密
就是使用一次異或加密,使用兩次異或解密
public void test4(){
String a = "sadfsdfsdfhfghf123dfgfg";
System.out.println(a);
int key = 324545231;
byte[] bytes = a.getBytes();
for (int i = 0; i < bytes.length-1; i++) {
bytes[i] = (byte)(bytes[i] ^ key);
}
String b = new String(bytes);
System.out.println(b);
for (int i = 0; i < bytes.length-1; i++) {
bytes[i] = (byte)(bytes[i] ^ key);
}
String c = new String(bytes);
System.out.println(c);
}
打印結果
sadfsdfsdfhfghf123dfgfg
����������������������g
sadfsdfsdfhfghf123dfgfg
10.源碼應用
可以參考JDK源碼:
Spliterator(當中定義了許多Characteristic,包括ORDERED、DISTINCT、SORTED等等),在集合類在使用到它的時候,會定義不同集合擁有不同的Characteristic,詳情見
ArrayList.ArrayListSpliterator的characteristics方法
HashMap.KeySpliterator的characteristics方法
...