Java位運算

在位運算前,需要先了解二進制碼相關知識,詳情請見博主的另一篇博文:原碼、反碼、補碼


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;
} 

這裏需要知道兩點:

  1. 任何數和自己進行異或操作結果都爲0
  2. 異或符合交換律,即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方法
...
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章