Java的二進制位操做整理

因爲 Java 是跨平臺語言,因此 JVM 表現下的基礎數據字節長度其實都是一致的。算法

int:4 個字節。    (1個字節是8位)測試

short:2 個字節。加密

long:8 個字節。spa

byte:1 個字節。code

float:4 個字節。orm

double:8 個字節。it

char:2 個字節。io

boolean:boolean屬於布爾類型,在存儲的時候不使用字節,僅僅使用 1 位來存儲,範圍僅僅爲0和1,其字面量爲true和false。form

原碼 反碼 補碼
咱們已經知道了一個 int 型數值是 4 個字節。每一個字節有 8 位。但對於一個 int 或者其它整數類型如 (long)的數值而言還要注意的是,它的最高位是符號位。class

最高位爲0表示正數。
最高位爲1表示負數
原碼 將一個數字轉換成二進制就是這個數值的原碼。
int a = 5; //原碼 0000 0000 0000 0000 0000 0000 0000 0101

int b = -3; //原碼 1000 0000 0000 0000 0000 0000 0000 0011

反碼
分兩種狀況:正數和負數

正數 正數的反碼就是原碼。
負數 負數的反碼是在原碼的基礎上,符號位不變 其它位都取反。
5 的原碼:0000 0000 0000 0000 0000 0000 0000 0101

-3 的原碼:1000 0000 0000 0000 0000 0000 0000 0011
-3 的反碼:1111 1111 1111 1111 1111 1111 1111 1100

補碼
仍然分正數和負數兩種狀況

正數 正數的補碼就是原碼。
負數 負數的補碼在反碼的基礎上加1。
5 的補碼:0000 0000 0000 0000 0000 0000 0000 0101


-3 的反碼:1111 1111 1111 1111 1111 1111 1111 1100
-3 的補碼: 1111 1111 1111 1111 1111 1111 1111 1101
計算機在進行數值運算的時候,是經過補碼錶示每一個數值的。

經過程序運行能夠驗證

public class Code {
    public static void main(String[] args) {
        int a = 5;
        int b = -3;
        String formata = String.format("%032d", Integer.parseInt(Integer.toBinaryString(a)));
        System.out.println(formata);
        System.out.println(Integer.toBinaryString(b));
    }
}

運行結果

00000000000000000000000000000101
11111111111111111111111111111101

若是a和b相加

100000000000000000000000000000010 此處爲33位,溢出,第一位1放棄,變爲

00000000000000000000000000000010   恰好爲2,    5-3=2 沒毛病。

程序驗證

public class Code {
    public static void main(String[] args) {
        int a = 5;
        int b = -3;
        String formata = String.format("%032d", Integer.parseInt(Integer.toBinaryString(a)));
        System.out.println(formata);
        System.out.println(Integer.toBinaryString(b));
        System.out.println(String.format("%032d",Integer.parseInt(Integer.toBinaryString(a + b))));
    }
}

運行結果

00000000000000000000000000000101
11111111111111111111111111111101
00000000000000000000000000000010

位運算符 &、|、~、^、>>、<<、>>>

& 與運算符
規則 與運算時,進行運算的兩個數,從最低位到最高位,一一對應。若是某 bit 的兩個數值對應的值都是 1,則結果值相應的 bit 就是 1,不然爲 0.

0 & 0 = 0,

0 & 1 = 0,

1 & 1 = 1
3 & 5 = 1 這是由於   (高位0省略)

0000 0011

&

0000 0101

=

0000 0001
按照規則,將兩個數值按照低位到高位一一對齊運算,由於只有第 0 位都爲 1,因此計算結果爲 1.

程序驗證

public class Code {
    public static void main(String[] args) {
        int a = 5;
        int b = 3;
        String formata = String.format("%032d", Integer.parseInt(Integer.toBinaryString(a & b)));
        System.out.println(formata);
        System.out.println(a & b);
    }
}

運行結果

00000000000000000000000000000001
1

判斷一個數的奇偶性能夠對1取與運算,結果爲1爲奇數,0爲偶數

| 或運算符
規則 或運算時,進行運算的兩個數,從最低位到最高位,一一對應。若是某 bit 的兩個數值對應的值只要 1 個爲 1,則結果值相應的 bit 就是 1,不然爲 0。

0 | 0 = 0,

0 | 1 = 1,

1 | 1 = 1
3 | 5 = 7 這是由於  (高位省略)

0000 0011

|

0000 0101

=

0000 0111

程序驗證

public class Code {
    public static void main(String[] args) {
        int a = 5;
        int b = 3;
        String formata = String.format("%032d", Integer.parseInt(Integer.toBinaryString(a | b)));
        System.out.println(formata);
        System.out.println(a | b);
    }
}

運行結果

00000000000000000000000000000111
7

~ 取反運算符

規則 對操做數的每一位進行操做,1 變成 0,0 變成 1。

~5 =>  0000 0101   ~  => 1111 1010

5取反後十進制數爲  (高位省略)

1111 1010     (最高位爲1,確定爲負數,此時爲補碼)
1111 1001      (補碼轉反碼-1)
1000 0110      (反碼轉原碼,此時爲-6)

因此5取反後爲-6

程序驗證

public class Code {
    public static void main(String[] args) {
        int a = 5;;
        System.out.println(Integer.toBinaryString(~a));
        System.out.println(~a);
    }
}

運行結果

11111111111111111111111111111010
-6

^ 異或運算符
規則 兩個操做數進行異或時,對於同一位上,若是數值相同則爲 0,數值不一樣則爲 1。

1 ^ 0 = 1,

1 ^ 1 = 0,

0 ^ 0 = 0;
3 ^ 5 = 6,這是由於 (高位省略)

0000 0011

^

0000 0101

=

0000 0110

程序驗證

public class Code {
    public static void main(String[] args) {
        int a = 5;
        int b = 3;   
        System.out.println(String.format("%032d",Integer.parseInt(Integer.toBinaryString(a ^ b))));
        System.out.println(a ^ b);
    }
}

運行結果

00000000000000000000000000000110
6

值得注意的是 3 ^ 5 = 6,而 6 ^ 5 = 3

0000 0110

^

0000 0101

=

0000 0011

public class Code {
    public static void main(String[] args) {
        int a = 5;
        int b = 6;
        System.out.println(String.format("%032d",Integer.parseInt(Integer.toBinaryString(a ^ b))));
        System.out.println(a ^ b);
    }
}

運行結果

00000000000000000000000000000011
3

針對這個特性,咱們能夠將異或運算做爲一個簡單的數據加密的形式。好比,將一個mp4文件全部數值與一個種子數值進行異或獲得加密後的數據,解密的時候再將數據與種子數值進行異或一次就能夠了。

因此說異或運算能夠做爲簡單的加解密運算算法。能夠說不少對稱加密算法都是根據異或進行擴展的,以後在DES,AES加密原理中會有說到。

>> 右移運算符
規則 a >> b 將數值 a 的二進制數值從 0 位算起到第 b - 1 位,總體向右方向移動 b 位,符號位不變,正數高位空出來的位補數值 0,負數補1。

5 >> 1 ===>  0000 0000 0000 0101 >> 1  = 0000 0000 0000 0010 = 2
7 >> 2 ===>  0000 0000 0000 0111 >> 2  = 0000 0000 0000 0001 = 1
9 >> 3 ===>  0000 0000 0000 1001 >> 3  = 0000 0000 0000 0001 = 1
11 >> 2 ===> 0000 0000 0000 1011 >> 2 = 0000 0000 0000 0010 = 2
規律:a >> b = a / ( 2 ^ b ) ,因此 5 >> 1= 5 / 2 = 2,11 >> 2 = 11 / 4 = 2。  (此處2 ^ b爲2的b次方)

咱們來看一下負數的右移

-5 >> 1

1000 0000 0000 0101   (-5原碼)

1111 1111 1111 1010    (-5反碼)

1111 1111 1111 1011     (-5補碼)

1111 1111 1111 1101      右移1位,符號位不變,負數高位補1

1111 1111 1111 1100      補碼轉反碼

1000 0000 0000 0011     反碼轉原碼,因此-5右移1位爲-3

程序驗證

public class Code {
    public static void main(String[] args) {
        int a = -5;
        System.out.println(Integer.toBinaryString(a >> 1));
        System.out.println(a >> 1);
    }
}

運行結果

11111111111111111111111111111101
-3

右移還有一個無符號右移>>>,對於正數來講,>>和>>>沒有什麼區別,但對於負數來講差異倒是巨大的

-5 >>> 1    (此處必須使用32位來計數)

1000 0000 0000 0000 0000 0000 0000 0101    -5原碼

1111 1111 1111 1111 1111 1111 1111 1010    -5反碼

1111 1111 1111 1111 1111 1111 1111 1011    -5補碼

0111 1111 1111 1111 1111 1111 1111 1101    無符號右移1位,高位補0

由於此時高位爲0,已經爲正數了,因此0111 1111 1111 1111 1111 1111 1111 1101就是結果的原碼,十進制爲2147483645

具體爲(2 ^ 30)+(2 ^ 29) +(2 ^ 28)+(2 ^ 27)+(2^ 26)+(2 ^ 25)+(2 ^ 24)+(2 ^ 23)+(2 ^ 22)+(2 ^ 21)+(2 ^ 20)+(2 ^ 19)+(2 ^ 18)+(2 ^ 17)+(2 ^ 16)+(2 ^ 15)+(2 ^ 14)+(2 ^ 13)+(2 ^ 12)+(2 ^ 11)+(2 ^ 10)+(2 ^ 9)+(2 ^ 8)+(2 ^ 7)+(2 ^ 6)+(2 ^ 5)+(2 ^ 4)+(2 ^ 3)+(2 ^ 2)+1 =2147483645

程序驗證

public class Code {
    public static void main(String[] args) {
        int a = -5;
        System.out.println("0" + Integer.toBinaryString(a >>> 1));
        System.out.println(a >>> 1);
    }
}

運行結果

01111111111111111111111111111101
2147483645

<< 左移運算符
規則 a << b 將數值 a 的二進制數值從 0 位算起到第 b - 1 位,總體向左方向移動 b 位,符號位不變,低位空出來的位補數值 0。

5 << 1 ===>  0000 0000 0000 0101 << 1  = 0000 0000 0000 1010 = 10
7 << 2 ===>  0000 0000 0000 0111 << 2  = 0000 0000 0001 1100 = 28
9 << 3 ===>  0000 0000 0000 1001 << 3  = 0000 0000 0100 1000 = 72
11 << 2 ===> 0000 0000 0000 1011 << 2 = 0000 0000 0010 1100 = 44
規律: a << b = a * (2 ^ b)       (此處2 ^ b爲2的b次方)

綜合上面兩個能夠看到,若是某個數值(正數)右移 n 位,就至關於拿這個數值去除以 2 的 n 次冪。若是某個數值(正數)左移 n 位,就至關於這個數值乘以 2 ^ n。

咱們來看一下負數左移

-5 << 1

1000 0000 0000 0101   (-5原碼)

1111 1111 1111 1010    (-5反碼)

1111 1111 1111 1011     (-5補碼)

1111 1111 1111 0110      左移1位

1111 1111 1111 0101      補碼轉反碼

1000 0000 0000 1010      反碼轉原碼,爲-10

因此-5 << 1 = -10

程序驗證

public class Code {
    public static void main(String[] args) {
        int a = -5;
        System.out.println(Integer.toBinaryString(a << 1));
        System.out.println(a << 1);
    }
}

運行結果

11111111111111111111111111110110
-10

由此看來,負數左移規律   -a << b = -a * (2 ^ b)   (此處2 ^ b爲2的b次方)

檢測第K位是否爲1       (如下運行結果均未補高位0)

已知數n,檢測其第K位(右起)是否爲1,能夠用如下表達式:

n & (1 << k - 1)     結果爲0,說明第K位爲0;結果不爲0,說明第K位爲1。

好比我要檢測965的二進制第3位是否爲1

public class Code {
    public static void main(String[] args) {
        int a = 965;
        int k = 1 << 3 - 1;
        System.out.println(Integer.toBinaryString(a));
        System.out.println(Integer.toBinaryString(a & k));
    }
}

運行結果

1111000101
100

說明第3位爲1

若是我要檢測第4位

public class Code {
    public static void main(String[] args) {
        int a = 965;
        int k = 1 << 4 - 1;
        System.out.println(Integer.toBinaryString(a));
        System.out.println(Integer.toBinaryString(a & k));
    }
}

運行結果

1111000101
0

說明第4位爲0

給一個數的第K位設置爲1

對於一個給定的操做數n,設置其第k位爲1,能夠用

n | (1 << k - 1)

好比我要設置965的第4位爲1

public class Code {
    public static void main(String[] args) {
        int a = 965;
        int k = 1 << 4 - 1;
        System.out.println(Integer.toBinaryString(a));
        System.out.println(Integer.toBinaryString(a | k));
        System.out.println(a | k);
    }
}

運行結果

1111000101
1111001101
973

第k位清零

將給定操做數n的第k位清零,能夠用表達式

n & ~(1 << k - 1)

好比我要將965的第3位清零

public class Code {
    public static void main(String[] args) {
        int a = 965;
        int k = 1 << 3 - 1;
        System.out.println(Integer.toBinaryString(a));
        System.out.println(Integer.toBinaryString(a & ~k));
        System.out.println(a & ~k);
    }
}

運行結果

1111000101
1111000001
961

切換第k位(1變0,0變1)

切換給定操做數n的第k位,能夠用表達式

n ^ (1 << k-1)

好比我要切換965的第4位

public class Code {
    public static void main(String[] args) {
        int a = 965;
        int k = 1 << 4 - 1;
        System.out.println(Integer.toBinaryString(a));
        System.out.println(Integer.toBinaryString(a ^ k));
        System.out.println(a ^ k);
    }
}

運行結果

1111000101
1111001101
973

切換第3位

public class Code {
    public static void main(String[] args) {
        int a = 965;
        int k = 1 << 3 - 1;
        System.out.println(Integer.toBinaryString(a));
        System.out.println(Integer.toBinaryString(a ^ k));
        System.out.println(a ^ k);
    }
}

運行結果

1111000101
1111000001
961

切換值爲1的最右位

切換給定操做數n的值爲1的最右位,能夠使用表達式

n & n - 1

仍是965

public class Code {
    public static void main(String[] args) {
        int a = 965;
        int k = a - 1;
        System.out.println(Integer.toBinaryString(a));
        System.out.println(Integer.toBinaryString(a & k));
        System.out.println(a & k);
    }
}

運行結果

1111000101
1111000100
964

隔離值爲1的最右位

隔離給定操做數n的值爲1的最右位,能夠使用表達式n & -n.

此次咱們使用966

public class Code {
    public static void main(String[] args) {
        int a = 966;
        int k = -a;
        System.out.println(Integer.toBinaryString(a));
        System.out.println(Integer.toBinaryString(a & k));
        System.out.println(a & k);
    }
}

運行結果

1111000110
10
2

最右位爲1是在第2位上,因此隔離爲10,末位0無心義,只是爲了說明最右位爲1是第2位而已。

隔離值爲0的最右位

隔離給定操做數n的值爲0的最右位,能夠使用表達式

~n & n + 1

此次依然使用965

public class Code {
    public static void main(String[] args) {
        int a = 965;
        int k = a + 1;
        System.out.println(Integer.toBinaryString(a));
        System.out.println(Integer.toBinaryString(~a));
        System.out.println(Integer.toBinaryString(k));
        System.out.println(Integer.toBinaryString(~a & k));
        System.out.println(~a & k);
    }
}

運行結果

1111000101
11111111111111111111110000111010
1111000110
10
2

結果10僅表示最右位爲0的爲第2位

檢查某個數是不是2的冪

給定一個數n,檢查其是否知足2 ^ n(2的n次方)的形式,能夠使用表達式

if (n & n - 1 == 0)

咱們使用968來測試一下

public class Code {
    public static void main(String[] args) {
        int a = 968;
        int k = a - 1;
        System.out.println(Integer.toBinaryString(a));
        if ((a & k) == 0) {
            System.out.println("a是2的冪");
        }else {
            System.out.println("a不是2的冪");
        }
    }
}

運行結果

1111001000
a不是2的冪

咱們用32768來測試一下

public class Code {
    public static void main(String[] args) {
        int a = 32768;
        int k = a - 1;
        System.out.println(Integer.toBinaryString(a));
        if ((a & k) == 0) {
            System.out.println("a是2的冪");
        }else {
            System.out.println("a不是2的冪");
        }
    }
}

運行結果

1000000000000000
a是2的冪

將某個數乘以2的冪

對於一個給定的數n,將其乘以2 ^ k (2的k次方),能夠使用表達式

n << k

假如咱們用965乘以2的5次方

public class Code {
    public static void main(String[] args) {
        int a = 965;
        int k = 5;
        System.out.println(Integer.toBinaryString(a));
        System.out.println((int)(a * Math.pow(2,k)));
        System.out.println(a << k);
    }
}

運行結果

1111000101
30880
30880

將某個數除以2的冪

給定操做數n,將其除以2 ^ k (2的k次方),能夠使用表達式

n >> k

假如咱們用965除以2的5次方

public class Code {
    public static void main(String[] args) {
        int a = 965;
        int k = 5;
        System.out.println(Integer.toBinaryString(a));
        System.out.println((int)(a / Math.pow(2,k)));
        System.out.println(a >> k);
    }
}

運行結果

1111000101
30
30

找到給定操做數的模

給定操做數n,計算其k(這裏k通常爲2的冪)的模(% k),能夠使用表達式n & k - 1

取數965對2的5次方取模

public class Code {
    public static void main(String[] args) {
        int a = 965;
        int k = 5;
        System.out.println(Integer.toBinaryString(a));
        System.out.println((int)(a % Math.pow(2,k)));
        System.out.println(a & ((1 << k) - 1));
    }
}

運行結果

1111000101
5
5

位值1的計數

統計位值1的計數方法有不少種,這裏主要講2種

一、按位處理

好比計算998的二進制數中有多少個1

public class Location {
    public static void main(String[] args) {
        int n = 998;
        System.out.println(Integer.toBinaryString(n));
        int count = 0;
        while (n != 0) {
            //count老是加末位的0或者1
            count += n & 1;
            //n右移1位
            n >>= 1;
        }
        System.out.println(count);
    }
}

運行結果

1111100110
7

二、使用切換方法:n & n - 1

public class Count {
    public static void main(String[] args) {
        int n = 998;
        System.out.println(Integer.toBinaryString(n));
        int count = 0;
        while (n != 0) {
            count++;
            //n和n-1每與操做一次就會消耗掉n的一個前位1
            n &= n - 1;
        }
        System.out.println(count);
    }
}

運行結果

1111100110 7

相關文章
相關標籤/搜索