Java位運算技巧
位運算作為底層的基本運算操作,往往是和'高效'二字沾邊,適當的運用位運算來優化系統的核心程式碼,會讓你的程式碼變得十分的精妙。以下是我所遇之的一些簡單的位運算技巧作為博文記錄。
1.獲得int型最大值
public static void main(String[] args) { int maxInt = (1 << 31) - 1; int maxInt1 = ~(1 << 31); int maxInt2 = (1 << -1) - 1; int maxInt3 = (-1>>>1); System.out.println("十進位制: "+ maxInt +" ,二進位制: " + Integer.toBinaryString(maxInt)); System.out.println("十進位制: "+ maxInt1 +" ,二進位制: " + Integer.toBinaryString(maxInt1)); System.out.println("十進位制: "+ maxInt2 +" ,二進位制: " + Integer.toBinaryString(maxInt2)); System.out.println("十進位制: "+ maxInt3 +" ,二進位制: " + Integer.toBinaryString(maxInt3)); } /** ~output~ 十進位制: 2147483647 ,二進位制: 1111111111111111111111111111111 十進位制: 2147483647 ,二進位制: 1111111111111111111111111111111 十進位制: 2147483647 ,二進位制: 1111111111111111111111111111111 十進位制: 2147483647 ,二進位制: 1111111111111111111111111111111 */
exp:int型別為32位,要獲得int的最大值,只需要最高位為0(正數),其餘位為1,便可得到最大數[01111111 11111111 11111111 11111111](2進位制)、[0xFFFFFFF](16進位制)。
2.獲得int型最小值
public static void main(String[] args) { int minInt = 1 << 31; int minInt1 = -1 << 31; int minInt2 = 1 << -1; System.out.println("十進位制: "+ minInt +" ,二進位制: " + Integer.toBinaryString(minInt)); System.out.println("十進位制: "+ minInt1 +" ,二進位制: " + Integer.toBinaryString(minInt1)); System.out.println("十進位制: "+ minInt2 +" ,二進位制: " + Integer.toBinaryString(minInt2)); System.out.println("十進位制: "+ 0x80000000 +" ,二進位制: " + Integer.toBinaryString(0x80000000)); } /** ~output~ 十進位制: -2147483648 ,二進位制: 10000000000000000000000000000000 十進位制: -2147483648 ,二進位制: 10000000000000000000000000000000 十進位制: -2147483648 ,二進位制: 10000000000000000000000000000000 十進位制: -2147483648 ,二進位制: 10000000000000000000000000000000 */
exp:int型別為32位,要獲得int的最大值,只需要最高位為1(負數),其餘位為0,便可得到最大數[10000000 00000000 00000000 00000000](2進位制)、[0x80000000](16進位制)。
負數最小二進位制和正數最大二進位制似乎有很大區別,這是因為cpu中只有加法器,減法只是加法的一種形式,而計算機是如何通過加法來計算減法的呢?
計算機對負數的實際表示是補碼形式,補碼的計算是以負數絕對值的原碼(二進位制)取反[不操作符號位],再加1得到,舉個例子:
- -7 的絕對值原碼(二進位制) = 1 000 0111 # 最高位為負數標記
- 1 000 0111取反 1 111 1000 # 符號位不取反
- 取反後加1 = 1 111 1000 + 1 = 1 111 1001 # 則得到-7的補碼 1 111 1001
- 計算 6 - 7 = 6 + (-7) = 0 000 0110 + 1 111 1001 = 1 111 1111
- 可以看到得到的結果為1 111 1111 最高位為1 ,結果為負數,是補碼的形式。我們反向推理,1 111 1111 - 1 = 1111 1110,取反(最高位符號位不操作) 1 111 1110取反得到原碼1 000 0001,所以可知十進位制值 -1。
3.乘以2的m次方或除以2的m次方
// 計算n*(2^m)
public static int mulTwoPower(int n,int m){
return n << m;
}
// 計算n/(2^m)
public static int divTwoPower(int n,int m){
return n >> m;
}
public static void main(String[] args) {
System.out.println("5 * 2 * 2 * 2 = "+ mulTwoPower(5, 3) );
System.out.println("6 / 2 / 2 = "+ divTwoPower(6, 2) );
}
/** ~output~
5 * 2 * 2 * 2 = 40
6 / 2 / 2 = 1
*/
我們知道十進位制是逢十進一,二進位制是逢二進一 ,十進位制*10,擴大原來的10倍,尾部多一個0,二進位制*2,擴大原來的2倍,尾數也多一個0,這便相當於二進位制數左移<<1位,0000 1111 * 2 = 0001 1110, 除以2則反之。
4.判斷奇偶數
// true 奇數 false 偶數
public static boolean isOddNumber(int n){
return (n & 1) == 1;
}
public static void main(String[] args) {
System.out.println("5 是 "+ isOddNumber(5) );
System.out.println("1234 是 "+ isOddNumber(1234) );
}
/** ~output~
5 是 true
1234 是 false
*/
這裡知識是二進位制最尾部的尾數為1,則此數必為奇數,尾數為0,此數必為偶數。所以通過與運算便可確定這個數的奇偶性。
5.對2的n次方取餘
public static int indexFor(int m, int n){
return m & (n - 1);
}
public static void main(String[] args) {
System.out.println("19 與 16 求餘 = "+ indexFor(19, 16) );
System.out.println("19 與 16 求餘 = "+ 19 % 16 );
}
/** ~output~
19 與 16 求餘 = 3
19 與 16 求餘 = 3
*/
此方法中n為2的指數值,則其二進位制形式的表示中只存在一個1,其餘位都為0,例如: 0000 1000、0100 0000、0010 0000等等。
則n-1的二進位制形式就為1的位數變為0,其右邊位全變為1,例如16的二進位制 0001 0000 -1 = 0000 1111
測試m為19的二進位制 0001 0011 & 0000 1111 = 0000 0011 = 3,地位保留的結果便是餘數。
此位運算也是HashMap中確定元素鍵(key)值所在雜湊陣列下標位置的核心方法,此位運算(hash & (length - 1))的效率極高於hash % length的求餘, 所以也解釋了為什麼HashMap的擴容始終為2的倍數(2的指數值)。
6.快速冪演算法,求n的m次方
// 快速冪演算法求n的m次方
public static int power(int n, int m) {
int temp = 1, base = n;
while (m != 0) {
if ((m & 1) == 1) { // 判斷奇偶,
temp = temp * base;
}
base = base * base;
m >>= 1; // 捨棄尾部位
}
return temp;
}
public static void main(String[] args) {
System.out.println("3的3次方 = " + power(3,3));
System.out.println("5的7次方 = " + power(5,7));
}
/** ~output~
3的3次方 = 27
5的7次方 = 78125
*/
我們知道,求n的m次方最簡單暴力的方法是迴圈m次求n的乘積,如今的計算機非常強大,這方面的效能似乎完全可以忽略優化,但對於微型機來說,程式碼優化應該是你時時刻刻需要考慮的,我們也從另外的問題出發:如何使用更少的時間複雜度去實現n的m次方呢?
快速冪演算法,下面來看下他的實現原理。
在數學中,存在這種等式n^m = n^(m1+m2+m3+.....+mk) = n^m1 * n^m2 * n^m3 * ...* n^mk, 且m1 + m2 + m3 +....+mk = m
我們計算m的二進位制,如上示例冪數7的二進位制=0000 0111,他的十進位制計算為: 1+2+4,所以5^7 = 5^(1+2+4) = 5^1 * 5^2 * 5^4,可以看出時間複雜度為f(n)=lgn