Java位運算理解和應用

原文轉載:http://blog.csdn.net/goskalrie/article/details/52796360

前言

日常開發中位運算不是很常用,但是巧妙的使用位運算可以大量減少運行開銷,優化算法。舉個例子,翻轉操作比較常見,比如初始值爲1,操作一次變爲0,再操作一次變爲1。可能的做法是使用三木運算符,判斷原始值爲1還是0,如果是1,設置爲0,否則設置爲0.但是使用位運算,不用判斷原始值,直接改變值就可以:

1^num//num爲原始值

當然,一條語句可能對代碼沒什麼影響,但是在高重複,大數據量的情況下將會節省很多開銷。

以下是自己整理的關於java位運算的部分內容,如有錯誤,還請指出,以共同進步,先行致謝。

1. 位運算符

1.1 java支持的位運算符:

&:按位與。

|:按位或。

~:按位非。

^:按位異或。

<<:左位移運算符。

>>:右位移運算符。

<<<:無符號右移運算符。

位運 算 符 中 ,除 ~ 以 外 ,其餘 均 爲 二 元 運 算 符 。 操 作 數 只 能 爲 整 型 和字 符 型 數 據 。

Java使用 補 碼 來 表 示 二 進 制 數 ,在補 碼 表 示 中 ,最高 位 爲 符號 位 ,正數 的 符 號 位 爲 0,負數 爲 1。補 碼 的 規 定 如 下 :

對 正 數 來 說 ,最高位爲 0,其餘 各 位 代 表 數 值 本 身 (以二 進制 表 示 ),如 +42的補碼 爲 00101010。

對 負 數 而 言 ,把該 數 絕 對 值 的 補 碼 按 位 取 反 ,然後 對 整 個數 加 1,即得 該 數的 補 碼 。 如 -1的補 碼 爲11111111111111111111111111111111(00000000000000000000000000000001按 位 取 反 11111111111111111111111111111110+1=11111111111111111111111111111111 )。爲何有那麼多0、1,java中int是32位的。

1.2   按位與(&)

按位與的運算規則

操作數1

0

0

1

1

操作數2

0

1

0

1

按位與

0

0

0

1

規則總結:只有兩個操作數對應位同爲1時,結果爲1,其餘全爲0. (或者是隻要有一個操作數爲0,結果就爲0)。

舉例:


 


 


 

1.3 按位或(|)

按位或的運算規則

操作數1

0

0

1

1

操作數2

0

1

0

1

按位或

0

1

1

1

規則總結:只有兩個操作數對應位同爲0時,結果爲0,其餘全爲1.(或者是隻要有一個操作數爲1,結果就爲1)。

1.4按位非(~)

按位非的運算規則

操作數

0

1

按位或

1

0

在求負數的源碼中使用過。

1.5 按位異或(^)

按位異或的運算規則

操作數1

0

0

1

1

操作數2

0

1

0

1

按位異或

0

1

1

0

規則總結:異:1.

1.6 左位移(<<)

算術右移(>>): 符號位不變,低位補0。如:2<<2結果爲8。


當移動的位數超過數字本身的位數時,那麼不就都需要補0操作,實際上不是的,java不可能做那麼浪費資源的事情。在真正執行位移前,其對要移動的位數做了一些預處理,比如32處理爲0,-1處理爲31.

1.7 右位移(>>)

低位溢出,符號位不變,並用符號位補溢出的高位。如:-6>>2結果爲-2。


 

1.8 無符號右移(>>>)

低位溢出,高位補0。注意,無符號右移(>>>)中的符號位(最高位)也跟着變,無符號的意思是將符號位當作數字位看待。如:-1>>>1結果爲2147483647。這個數字應該比較熟悉,看兩個輸出語句就知道是什麼了:

System.out.println(Integer.toBinaryString(-1>>>1));

System.out.println(Integer.toBinaryString(Integer.MAX_VALUE));

輸出結果爲:

1111111111111111111111111111111

1111111111111111111111111111111

-1>>>1竟然得到了int所能表示的最大整數,精彩。


除了使用-1>>>1能得到Integer.MAX_VALUE,以下的也能得到同樣的結果:

        //maxInt

        System.out.println(~(1 << 31));

        System.out.println((1 << -1)-1);

        System.out.println(~(1 << -1));

使用位運算往往能很巧妙的實現某些算法完成一些複雜的功能。

常見使用

1.      m*2^n

可以使用m<<n求得結果,如:

        System.out.println("2^3=" + (1<<3));//2^3=8

        System.out.println("3*2^3=" + (3<<3));//3*2^3=24

計算結果是不是很正確呢?如果非要說2<<-1爲什麼不等於0.5,前面說過,位運算的操作數只能是整型和字符型。在求int所能表示的最小值時,可以使用

//minInt

System.out.println(1 << 31);

System.out.println(1 << -1);

可以發現左移31位和-1位所得的結果是一樣的,同理,左移30位和左移-2所得的結果也是一樣的。移動一個負數位,是不是等同於右移該負數的絕對值位呢?輸出一下就能發現不是的。java中int所能表示的最大數值是31位,加上符號位共32位。在這裏可以有這樣的位移法則:

法則一:任何數左移(右移)32的倍數位等於該數本身。

法則二:在位移運算m<<n的計算中,若n爲正數,則實際移動的位數爲n%32,若n爲負數,則實際移動的位數爲(32+n%32),右移,同理。

左移是乘以2的冪,對應着右移則是除以2的冪。

2.      判斷一個數n的奇偶性

n&1 == 1?”奇數”:”偶數”

爲什麼與1能判斷奇偶?所謂的二進制就是滿2進1,那麼好了,偶數的最低位肯定是0(恰好滿2,對不對?),同理,奇數的最低位肯定是1.int類型的1,前31位都是0,無論是1&0還是0&0結果都是0,那麼有區別的就是1的最低位上的1了,若n的二進制最低位是1(奇數)與上1,結果爲1,反則結果爲0.

3.      不用臨時變量交換兩個數

在int[]數組首尾互換中,是不看到過這樣的代碼:

[java] view plain copy
  1. public static int[] reverse(int[] nums){  
  2.     int i = 0;  
  3.     int j = nums.length-1;  
  4.     while(j>i){  
  5.         nums[i]= nums[i]^nums[j];  
  6.         nums[j] = nums[j]^nums[i];  
  7.         nums[i] = nums[i]^nums[j];  
  8.         j--;  
  9.         i++;  
  10.     }  
  11.     return nums;  
  12. }  
連續三次使用異或,並沒有臨時變量就完成了兩個數字交換,怎麼實現的呢?


上面的計算主要遵循了一個計算公式:b^(a^b)=a。

我們可以對以上公式做如下的推導:

任何數異或本身結果爲0.且有定理a^b=b^a。異或是一個無順序的運算符,則b^a^b=b^b^a,結果爲0^a。

再次列出異或的計算表:

操作數1

0

0

1

1

操作數2

0

1

0

1

按位異或

0

1

1

0

可以發現,異或0具有保持的特點,而異或1具有翻轉的特點。使用這些特點可以進行取數的操作。

         那麼0^a,使用異或0具有保持的特點,最終結果就是a。

其實java中的異或運算法則完全遵守數學中的計算法則:

①    a ^ a =0

②    a ^ b =b ^ a

③    a ^b ^ c = a ^ (b ^ c) = (a ^ b) ^ c;

④    d = a ^b ^ c 可以推出 a = d ^ b ^ c.

⑤    a ^ b ^a = b.

4.      取絕對值

(a^(a>>31))-(a>>31)

先整理一下使用位運算取絕對值的思路:若a爲正數,則不變,需要用異或0保持的特點;若a爲負數,則其補碼爲源碼翻轉每一位後+1,先求其源碼,補碼-1後再翻轉每一位,此時需要使用異或1具有翻轉的特點。

任何正數右移31後只剩符號位0,最終結果爲0,任何負數右移31後也只剩符號位1,溢出的31位截斷,空出的31位補符號位1,最終結果爲-1.右移31操作可以取得任何整數的符號位。

那麼綜合上面的步驟,可得到公式。a>>31取得a的符號,若a爲正數,a>>31等於0,a^0=a,不變;若a爲負數,a>>31等於-1 ,a^-1翻轉每一位.

小結

在日常的java開發中位運算使用的不是很常見,但是面試或考試中會有涉及的地方,雖然不是決定項,但卻是加分項,說明對計算機語言有最起碼的瞭解。而且在高級算法中,位運算往往能優化算法運行效率,減少運行時間。再比如,有一張全是選擇題或是勾選題(類似判斷)的試卷,你是使用每個選項一條記錄的形式保存答案還是使用一個二進制對應的整數來保存答案?就像是英語考試中的答題卡:


每個題目有4個選項,每個選項有兩個狀態:選、不選(1、0),那麼此時是不是可以使用4位二進制數來表示某題的答案呢?


原文轉載:http://blog.csdn.net/goskalrie/article/details/52796360


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章