1. 程式人生 > >Java位運算技巧

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得到,舉個例子:  

  1.      -7 的絕對值原碼(二進位制) = 1  000  0111            # 最高位為負數標記
  2.      1  000  0111取反  1 111 1000                            # 符號位不取反
  3.      取反後加1  =  1 111 1000  + 1 = 1 111 1001      # 則得到-7的補碼 1 111 1001
  4.      計算 6 -  7 = 6 + (-7) = 0 000 0110 + 1 111 1001 = 1 111 1111
  5.      可以看到得到的結果為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