現在系統實現中,加法操作與移位操作運算速度差距不大,但是移位操作在做乘法的時候要快於乘法(減法是變相的加法,除法是變相的乘法)。在一些對運算速度要求高的系統中,移位操作往往能增加不少的效率。
要掌握移位操作符,首先要對二進制有一定的了解。
jdk中計算某一個二進制數之中1的數量的代碼:
public static int bitCount(int i) { // HD, Figure 5-2 i = i - ((i >>> 1) & 0x55555555); i = (i & 0x33333333) + ((i >>> 2) & 0x33333333); i = (i + (i >>> 4)) & 0x0f0f0f0f; i = i + (i >>> 8); i = i + (i >>> 16); return i & 0x3f; }
如果不清楚十進制如何轉換成二進制,可以這樣打印出來。
System.out.println(Integer.toBinaryString(2));//結果是10
二進制表示法,我們以int類型的2舉例。運行結果是10,但是實際上它是 0000 0000 0000 0000 0000 0000 0000 0010。
(一般的,我們將左16位作為高位,右16位作為低位)。最高為為符號位,0代表正數,1代表負數
不過如果你打印這樣的二進制。
System.out.println(00000000000000000000000000000010);
運行的結果居然是8!這不是二進制表示法除了問題,也不是編譯器除了問題。編譯器默認將操作數當成十進制做處理,以0開頭的,編譯器都會將它作為八進制處理,以0x開頭就是16進制。
再測試一個 -2。
System.out.println(Integer.toBinaryString(-2));//11111111111111111111111111111110
Java是數值表示是基於二進制補碼的。求一個數的負數,先對所有位求反,0變1,1變0,然后加一。就這個例子來說,先把2的二進制00000000000000000000000000000010 對每一位求反,獲得結果11111111111111111111111111111101然后加1,最后結果是11111111111111111111111111111110。
二進制粗略的介紹就到這里,接下來介紹移位操作。
右移一個單位(>>1):往高位插入一個0,其他位都右移(相當於除以2)。左移一個檔位(<<1):低位插入一個0,其他位都左移(相當於乘以2)。<< 和 >> 都是帶符號的操作符,在移位后,如果數值本來是正的,最高位補0,負的就補1。其他的一位操作符還有 >>>,無符號右移操作符,移位后不會去補符號位(可見,這個操作符運算速度要快於有符號的移位操作符)
接下來看一個有趣的例子。例子來自Java puzzles。
public static void main(String[] args) { int i = 0; while (-1 << i != 0) i++; System.out.println(i); }
按照我們所知道的知識, -1可16進制表示為0xffffffff(二進制為11111111111111111111111111111111)。int 類型的-1是32位都被置位的數值,每一次右移,都在低位插入一個0,第一次變成了:11111111111111111111111111111110,第二次變成了11111111111111111111111111111100…………所以循環了32次然后打印出我們32??但是在運行的時候,我們發現它不打印任何數值,而是進入了一個無限循環。
這個問題的謎題就在於-1移動32位的結果不是0,而是返回自身(我並不清楚底層是怎么做到的)。
在移動到 31位的時候:
System.out.println(-1 << 31);//-2147483648 System.out.println(Integer.toBinaryString(-2147483648));//10000000000000000000000000000000
但是移動到32位的時候變回了-1.
System.out.println(-1 << 32);
對於這個問題,我們可以打印一下Integer.MIN_VALUE。它表示int類型的最小數值。
System.out.println(Integer.MIN_VALUE);//-2147483648 System.out.println((-1 << 31) == Integer.MIN_VALUE);//true
我們發現-1左移31位的時候就已經等於 Integer.MIN_VALUE了。我們再比較一下
int i = -1 << 31 ; System.out.println(i << 1);//0 System.out.println(-1 << 32);//-1 System.out.println(Integer.MIN_VALUE<<1);//0
這個意思就是,不能一下子丟棄所有的位。
關於移位操作,它還有這樣的規定,對於int 類型,移位只有對低5位作為移動長度,對於long,為6位。(2的5次方32位,2的6次方64位)。而且移位長度是對32取余的,也就是說長度在0到31之間,對於long,在0到63之間。沒有任何的移位方法可以讓一個數值丟棄所有的位。
不過為了打印出 32,我們可以這樣處理。這個循環可以輸出32。
public static void main(String[] args) { int distance = 0; for (int val = -1; val != 0; val <<= 1) distance++; System.out.println(distance); }
這個解題思路和前一個不同。前一循環判斷可以一次性移動多少位,而這個循環每移動一位就存儲到變量中。
這里還有一個很有趣的問題:
System.out.println(Integer.MIN_VALUE<<1);//-2147483648 System.out.println(Integer.MAX_VALUE);//2147483647 System.out.println(Math.abs(Integer.MIN_VALUE));//-2147483648
int最小值的絕對值比最大值大1.而且Math的abs方法對最小值取絕對值還是最小值。
接下來再看一個例子
public static void main(String[] args) { int i = -1 ; int count = 0; while( i != 0){ i >>>= 1;
//System.out.println(i); count++; } System.out.println(count); }
接受了上一次的教訓,也許你要脫口而出,無限循環!!!!我們分析這個程序,初始的時候,-1滿足條件進入循環,然后做無符號右移操作.第一次移位操作,-1就變成了2147483647,之后每一次右移就是除以2。把注釋去掉,可以打印如下結果。
2147483647 1073741823 536870911 268435455 134217727 67108863 33554431 16777215 8388607 4194303 2097151 1048575 524287 262143 131071 65535 32767 16383 8191 4095 2047 1023 511 255 127 63 31 15 7 3 1 0
接下來再看一段代碼
public static void main(String[] args) { short i = -1 ; int count = 0; while( i != 0){ i >>>= 1; // System.out.println(i); count++; } System.out.println(count); }
這段代碼和上一段代碼幾乎一模一樣,第一反應是16。short 類型的存儲空間就是16位,再一想就迷糊了,byte,short,char三種類型進行運算的時候自動會轉型成int類型,那么答案是32呢還是16呢。
不過運行這個程序,也許會讓你大吃一驚。居然是一個無限循環!!!
首先在進行移位操作前,short類型的i被提升為int類型進行操作。這樣原來的short 的-1(0xffff)變成了int的-1(0xffffffff)。接下來是無符號的右移,變成了int的 0x7fffffff(這個數字是2147483647)。>>>=是一個混合類型操作,第一步是移位,第二部就要賦值了。於是接下來發生了一個可怕的操作,要把int的數值存儲到short類型的變量中,要進行窄化的原始類型轉換。執行的結果是高位直接被截斷,int的0x7fffffff又變成了short 的-1(0xffff)。於是進入了一個無限循環。
