Java 位運算超全面總結(以及Koltin)

1.原碼、反碼、補碼

關於原碼、反碼、補碼的相關知識做者不打算在這裏長篇大論,相關知識已有別的大佬總結很好了,還請老鐵自行 Google,不過有篇知乎回答是做者學編程以來見過對相關知識最通俗易懂,生動簡潔的解釋:對原碼、反碼、補碼最通俗易懂,生動簡潔的解釋,牆裂建議你們先看完這篇科普文章。在繼續討論以前你要先明白一點:整數在計算機內部都是以補碼形式存儲的android

2.Java 位運算概覽

OK 都看到這兒了那我就假定你已經掌握了原碼、反碼、補碼相關知識(雖然上面那段幾乎啥也沒講,純湊字數)git

不廢話了。github

首先,在程序中,位運算符的優先級很低!且位運算是隻針對整型(是的這裏指全部整型)數據的,不管正負,先搞清楚這兩點,咱們繼續說。面試

Java 中的位運算共有 與(&)、或(|)、非(~)、異或(^)、左移(<<)、右移(>>)、無符號右移(>>>) 這7種,概覽見下表:算法

符號 含義 詳解
& 位與 兩個比特位都爲 1 時,結果才爲 1,不然爲 0 (位與操做知足交換律和結合律,甚至分配律)
| 位或 兩個比特位都爲 0 時,結果才爲 0,不然爲 1 (位或操做知足交換律和結合律,甚至分配律)
~ 位非 即按位取反,1 變 0,0 變 1
^ 異或 兩個比特位相同時(都爲 0 或都爲 1)爲 0,相異爲 1(異或操做知足交換律和結合律,甚至分配律。任何整數和本身異或的結果爲 0,任何整數與 0 異或值不變)
<< 左移 將全部的二進制位按值向←左移動若干位,左移操做與正負數無關,它只是傻傻地將全部位按值向左移動,高位捨棄,低位補 0
>> 右移 將全部的二進制位按值向右→移動若干位,低位直接捨棄,跟正負無關,而高位補 0 仍是補 1 則跟被操做整數的正負相關,正數補 0 ,負數補 1
>>> 無符號右移 將全部的二進制位按值向右→移動若干位,低位直接捨棄,跟正負無關,高位補 0 ,也跟正負無關

這裏有幾點須要注意:編程

  1. 這七個位運算符,只有取反(~)運算符是單目運算符,其餘均爲雙目運算符!
  2. 注意上面提到的某幾個位運算操做知足交換律和結合律甚至分配律,這很重要!後面你會看到。
  3. 額好吧我目前只想到上面兩點須要注意的,別的想到我會更新文章!

還要說一下在 Kotlin 裏面,位運算跟 Java 一致也是這七種,只不過運算符號跟 Java 裏有點不太同樣,Kotlin 裏的七種位運算符:數組

  • and(bits) – 位與 (Java 的 &)
  • or(bits) – 位或 (Java 的 |)
  • inv() – 位非 (Java 的 ~)
  • xor(bits) – 位異或 (Java 的 ^)
  • shl(bits) – 有符號左移 (Java 的 <<)
  • shr(bits) – 有符號右移 (Java 的 >>)
  • ushr(bits) – 無符號右移 (Java 的 >>>)

嗯除了寫法其餘跟 Java 中一致!bash

下面咱們來看些 Java 中位運算的代碼實例。網絡

3.Java 位運算最佳實踐(常見操做)

1.位運算基礎操做

//對於任意整數 a 和 b,如下表達式的結果均爲 true,各位讀者朋友可自行驗證,真不騙你!
//注意如下幾個操做之因此一一在這裏特別列出,是由於它們對於位操做而言具備某種廣泛意義,也就是說你最好能將它們牢記於心。
//至於它們有怎樣的廣泛意義,留給讀者們自行思考,總之記住實踐出真知!
a|0 == a;
a&-1 == a;
a&0 == 0;

a^a == 0;
a^0 == a;

a|~a == -1;
a&~a == 0;
a&a == a;
a|a == a;

a|(a&b) == a;
a&(a|b) == a;
複製代碼

2.位運算進階操做(部分操做參考了別的大佬博客):

1.判斷奇偶

能夠很簡單概括出,只需判斷一個正整數的二進制補碼最後一位是0是1,是0爲偶數,是1爲奇數編程語言

public void isOddOrEven(int n){
        if ((n & 1) == 1) {//n是奇數
            System.out.println("Odd");
        }else {//n是偶數
            System.out.println("Even");
        }
    }
複製代碼
2.省去中間變量交換兩整數的值(聽說面試常考)
public void swap(){
        int a = 1, b = 2;
        a ^= b;
        b ^= a;//b == 1
        a ^= b;//a == 2
        System.out.println("a:" + a);//a:2
        System.out.println("b:" + b);//b:1
    }
複製代碼
3.變換符號,正變負,負變正

只需對待操做數應用取反操做後再加 1 便可

public void negate(){
        int a = -10, b = 10;
        System.out.println(~a + 1);//10
        System.out.println(~b + 1);//-10
    }
複製代碼
4.求絕對值,

對於負數能夠經過上面變換符號的操做獲得絕對值, 正數直接返回便可,所以咱們要先判斷符號位來得知當前數的正負。

public int abs(int a){
        int i = a >> 31;//獲得符號位,0 爲正數,-1 爲負數
        return i == 0 ? a : (~a + 1);//符號位爲 0 直接返回,不然返回 ~a + 1
    }
複製代碼

或者,n>>31 取得n的符號,若n爲正數,n>>31等於0,若n爲負數,n>>31等於-1 若n爲正數 n^0-0 數不變,若n爲負數n^-1 須要計算n和-1的補碼,異或後再取補碼, 結果n變號而且絕對值減1,再減去-1就是絕對值

public int abs(int a){
        return a ^ (a >> 31)) - (a >> 31);
    }
複製代碼
5. 判斷一個數num是否是 2 的冪

若是是2的冪,n必定是100... (也就是二進制位裏只有一個是1,且是首位,其他全是0),n-1就是011.... 因此作與運算結果爲 0,這裏貼下 Kotlin 代碼:

fun isPowerOfTwo(num: Int): Boolean {
        if (num < 1){
            return false
        }
        return num.and(num - 1) == 0
    }
複製代碼
6.判斷一個數num是否是 4 的冪

理論上數字4冪的二進制相似於100,10000,1000000,... 這些形式。 不難概括出以下結論: 4的冪必定是2的。 4的冪和2的冪同樣,二進制位裏只有一個是1,且是首位。可是,4的冪中的1老是出如今奇數位。 這裏貼下 Kotlin 代碼(非最佳實現):

fun isPowerOfFour(num: Int): Boolean {
        if (num < 1){
            return false
        }
        return (num.and(num - 1) == 0 && Integer.toBinaryString(num).length.and(1) == 1)
    }
複製代碼
其餘進階操做
// 7. int 型最大值是什麼?
System.out.println((1 << 31) - 1);// 2147483647, 注意運算符優先級,括號不可省略
System.out.println(~(1 << 31));// 2147483647

// 8. int 型最小值是什麼?
System.out.println(1 << 31);
System.out.println(1 << -1);

// 9. long 型最大值是什麼?
System.out.println(((long)1 << 127) - 1);

// 10. 整數n乘以2是多少?
n << 1;

// 11. 整數n除以2是多少?(負奇數的運算不可用)
n >> 1;

// 12. 乘以2的n次方,例如計算10 * 8(8是2的3次方)
System.out.println(10<<3);

// 13. 除以2的n次方,例如計算16 / 8(8是2的3次方)
System.out.println(16>>3);

// 14. 取兩個數的最大值(某些機器上,效率比a>b ? a:b高)
System.out.println(b&((a-b)>>31) | a&(~(a-b)>>31));

// 15. 取兩個數的最小值(某些機器上,效率比a>b ? b:a高)
System.out.println(a&((a-b)>>31) | b&(~(a-b)>>31));

// 16. 判斷符號是否相同(true 表示 x和y有相同的符號, false表示x,y有相反的符號。)
System.out.println((a ^ b) > 0);

// 17. 計算2的n次方 n > 0
System.out.println(2<<(n-1));

// 18. 求兩個整數的平均值
System.out.println((a+b) >> 1);

// 19. 從低位到高位,取n的第m位
int m = 2;
System.out.println((n >> (m-1)) & 1);

// 20. 從低位到高位.將n的第m位置爲1,將1左移m-1位找到第m位,
//得000...1...000,n 再和這個數作或運算
System.out.println(n | (1<<(m-1)));

// 21. 從低位到高位,將n的第m位置爲0,將1左移m-1位找到第m位,
//取反後變成111...0...1111,n再和這個數作與運算
System.out.println(n & ~(0<<(m-1)));

// 22 暫時木有了,想到再加~
複製代碼

4.Java 位運算趣味應用

1.只出現一次的數字

給定一個非空整數數組,除了某個元素只出現一次之外,其他每一個元素均出現兩次。找出那個只出現了一次的元素。

解法: 解題思路其實很簡單,就是上面提到的異或操做知足交換律和結合律,任何整數和本身異或的結果爲 0,任何整數與 0 異或其值不變,我直接貼下 Kotlin 代碼

fun singleNumber(nums: IntArray): Int {
        var result = 0
        nums.forEach { 
            result = result.xor(it)
        }
        return result
    }
複製代碼

2.尋找缺失數字

給定一個包含 0, 1, 2, ..., n 中 n 個數的序列(無序排列),找出 0 .. n 中沒有出如今序列中的那個數。

  • 示例 1: 輸入: [0,1,3] 輸出: 2

  • 示例 2: 輸入: [9,6,4,2,3,5,7,0,1] 輸出: 8

解法: 解題思路其實很簡單,仍是上面提到的異或操做知足交換律和結合律,任何整數和本身異或的結果爲 0,任何整數與 0 異或其值不變,我直接貼下 Kotlin 代碼

fun missingNumber(nums: IntArray): Int {
        var sum = nums.size
        nums.forEachIndexed { index, i ->
            sum = sum.xor(index)
            sum = sum.xor(i)
        }
        return sum
    }
複製代碼

3.額好吧暫時只想到兩條,想到別的會更新文章加上

未完待續…

4.Java 位運算操做庫

咱們以 Java 標準庫的 Integer 類爲例,emmm…Java 標準庫裏面封裝好的全部位操做方法還請你們自行翻閱 Java 標準庫文檔哈!我在這裏只舉幾個標準庫裏面容易被你們忽略的有用方法。 主要有三個:

//返回指定int值的二進制補碼錶示形式中的值爲1的位的個數。
Integer.bitCount(int i)

//使用
//17的二進制補碼錶示是10001,能夠看到裏面共有兩個位值爲1
Integer.bitCount(17) == 2//true
複製代碼
//把指定int值的二進制補碼除了值爲1的最高位以外的全部非最高位值爲1的位——置值爲0返回。
Integer.highestOneBit(int i)

//使用
//十進制數 36 的二進制補碼錶示爲100100,應用 highestOneBit 操做後返回二進制補碼100000,即十進制的32
Integer.highestOneBit(36) == 32//true
複製代碼
//把指定int值的二進制補碼除了值爲1的最低位以外的全部非最低位值爲1的位——置值爲0返回。
Integer.lowestOneBit(int i)

//使用
//十進制數 36 的二進制補碼錶示爲100100,應用 lowestOneBit 操做後返回二進制補碼100,即十進制的4
Integer.lowestOneBit(36) == 4//true

複製代碼

5.總結

主要內容以上,不知在座各位看完本文後心裏做何感想,我來幫你們總結下吧:位運算操做是不少編程語言的基礎組成部分,它初看起來好像沒什麼卵用,不少人在學習編程時初次接觸位運算操做心裏可能也是比較懵逼,不知道這東西能用來幹嗎,索性就一筆帶過了!不過從上面咱們的總結來看,位運算操做由於所處位置比較接近計算機底層,因此合理利用,每每能達到四兩撥千斤的效果!單從android編程來看,咱們在加密算法還有網絡包處理等業務上使用位運算的頻率仍是很高的,更別提Intent中的那些種類繁多的Flag以及View佈局裏那些難以一言道盡的常量值了(好比Gravity裏各個用位或運算符拼接的常量值),所以學好這方面的基礎知識仍是很重要的。

固然,還有一樣很重要的編程技術面試,位運算也是一大考察點呢!

另外,做者水平有限,文中的技術錯誤確定不敢說沒有(雖然寫這篇文章我還專門留時間檢查了幾遍),另外相關知識點總結的未必全面,文中恐有疏漏,歡迎各位同行評論區不吝賜教!

做者 github:點這兒

歡迎 follow

未完待續…

相關文章
相關標籤/搜索