二進位制究竟有什麼用?齊姐帶你看看那些好玩兒的「位操作」

碼農田小齊發表於2020-08-25

本篇終於講到了齊姐文章裡常常出現的分割線!

計算機說到底就是 0 和 1,所有的數在記憶體中都是以二進位制的形式儲存的。

而位操作,或者說位運算,就是直接對記憶體中的二進位制位進行操作。

位運算可以說是我們的基本功,今天這篇文章就從以下角度和大家一起玩轉位運算。

  1. 位運算究竟有什麼用?
  2. 原碼 反碼 補碼
  3. 7 種位運算

當然了,位運算還有很多奇技淫巧,如果大家還想看進階篇,記得給我點贊或者留言告訴我哦~

位運算的作用

在實際生產中,位運算是用來優化時間和空間的。

位運算可能並不會降低複雜度的等級,但是可以把複雜度前面的係數降下來。

舉個例子。

大家都知道堆,或者叫優先佇列,一般來說是用完全二叉樹來實現的,叫做二叉堆

最小堆
最小堆

二叉堆插入、刪除元素的時間複雜度都是 O(logn),如果這個不清楚的同學趕緊在公眾號內回覆「」複習一下,或者點選這裡

但是有另一種堆,它能夠做到 O(1) 的時間插入元素,O(logn) 的時間刪除元素,我在堆這篇文章裡也提到過,就是斐波那契堆

但為什麼不用呢?

就是因為 O(1) 前面的係數非常大。

我們說 O(logn)O(1) 好,是有個條件的,那就是 n 非常非常大的情況下,但是實際上,如果 n 是在 int 範圍內,那麼取個 log 也不過就是 32 了,反而這個 O(1) 的時間複雜度可能係數達到幾百幾千。

一般來說實際應用中時間的測量並不是像我們做演算法題時用時間複雜度這麼簡單,有的時候就需要你把兩個演算法都實現出來,去跑去測量它的時間,才能決定哪個好。

那麼二進位制一次能夠作用於 32 位上(假設是一個 int),如果資料表示的巧妙,這完全可以優化 32 倍,多用幾個 int 就多優化了好幾個 32 倍,不香嗎?

除了優化時間,還可以優化空間。

比如在網站釋出新版本時,一般都會附上支援該版本的瀏覽器列表,不然有些老掉牙的瀏覽器看不到我的新功能還算我的鍋麼?

那麼怎麼有效的表示這個瀏覽器列表呢?

全世界所有瀏覽器都有個國際標準編號,這裡我就簡單假設一下:

  • 0 表示 QQ 瀏覽器
  • 1 表示 Chrome 瀏覽器
  • 2 表示火狐瀏覽器
  • 3 表示 ...

那麼我們就可以用一個 int 表示是否支援這些瀏覽器的狀態,如果這個瀏覽器能用,那麼這一位上就設為 1,不能用就設為 0,這樣用一個 int 就能表示 32 個網站。

那麼國內的某個網站可以表示為:

  • 0b .... 1101

所以位操作在很多程式碼裡都很常用,比如網路協議、作業系統等等。

這就是位運算的兩大優勢,接下來我們說說具體的知識點。

原碼 反碼 補碼

數字有正有負,Java 中用的是 signed type,就是有正有負的。

雖然在 Java 8 之後,也用了個工具來實現 unsigned type,但是其實底層實現是沒有的。

二進位制最左邊的一位是符號位,

  • 0 表示這個數是非負數;
  • 1 表示這個數是負數。

對了,最左邊的一位英文叫做 most significant bit,別說錯了。。。

正數

正數的原碼反碼補碼相同,沒啥好說的。

比如:

  • int 1 = 0b 0000 0000 0000 0001
  • int 2 = 0b 0000 0000 0000 0010

負數:

原碼:把相應的正數的符號位設為 1。

  • -1 的原碼 = 0b 1000 0000 0000 0001
  • -2 的原碼 = 0b 1000 0000 0000 0010

反碼 ones' complement

符號位是 1,其餘位取反。

  • -1 的反碼 = 0b 1111 1111 1111 1110
  • -2 的反碼 = 0b 1111 1111 1111 1101

補碼 two's complement

反碼 + 1。

  • -1 的補碼 = 0b 1111 1111 1111 1111
  • -2 的補碼 = 0b 1111 1111 1111 1110

而計算機中真正用來儲存資料的是用補碼

這裡稍微注意下反碼和補碼的英文,ones' 的這個 ' 在後面,two's 的這個 ' 在中間。。

為什麼計算機要用補碼來儲存資料呢?

可能有同學會說正零負零的原因,但這只是表面現象。

實際上通過補碼這樣精巧的設計,計算機做加減乘除運算就不用考慮符號,就可以讓硬體裡 CPU 的設計變得異常簡單。

最初計算機只有加法器沒有減法器,所以它用這麼一種方式用加法完成了減法。

int 的最大值是多少?

正是因為最左邊一位是符號位,所以正數的表示就少了一位能用的,那麼 int 的最大值就是:

0111111...11 (31 ones) = 2^31 - 1 = 2147483647

7 種位運算

運算子 中文 英文 運算規則
<< 左移 left shift 右邊補充 0
>> 右移 signed right shift 左邊補充符號位
>>> 無符號右移 unsighed right shift Java 特有,左邊補充 0
~ 位非 NOT 每位取反
& 位與 bitwise AND 每位做與操作,都是 1 則為 1,否則為 0
I 位或 OR 每一位做或操作,有 1 則為 1,否則為 0
^ 異或 XOR 相同為 0,不同為 1

要注意的是前 4 個運算子是對 1 個數進行操作的,且操作完成後這個數本身的值不變;後 3 個操作是兩個數的運算。

我們一一來看。

為了書寫方便,下面的數值雖然是 int 型別,但我只寫 8 位,大家都能理解的噢!

1. <<

  • 1 = 0b 0000 0001

  • 1 << 1 = 0b 0000 0010 = 2

  • 2 = 0b 0000 0010

  • 2 << 1 = 0b 0000 0100 = 4

  • 3 = 0b 0000 0011

  • 3 << 1 = 0b 0000 0110 = 6

誒,大家發現沒有,左移 1 位之後這個數相當於乘2

但是這隻適用於左邊溢位的高位中不包含 1 時。

如果把 1 扔了,那就肯定不是 2 倍了嘛。

2. >>

  • 1 = 0b 0000 0001

  • 1 >> 1 = 0b 0000 0000 = 0

  • 2 = 0b 0000 0010

  • 2 >> 1 = 0b 0000 0001 = 1

  • 3 = 0b 0000 0011

  • 3 >> 1 = 0b 0000 0001 = 1

同理,右移操作的效果是這個數除以2

如果是負數呢?

  • -3 = 1111 1101
  • -3 >> 1 = 1111 1110 = -2

效果也是一樣的,因為左邊補充符號位。

3. >>>

和 >> 的不同之處在於,這個的左邊不論正負,一律補充 0。

所以對於正數來說,和 >> 的效果一樣,但是負數不同。

  • -3 = 1111 1101
  • -3 >> 1 = 01111 1110 = 很大的數。。

4. ~

取反操作,就是每一位取反,1變成0,0變成1。

  • 3 = 0b 0000 0011
  • ~3 = 0b 1111 1100

5. &

這個符號其實和邏輯與運算 && 意思一樣,只不過作用在每一位上。

對於每一位來說,兩個數都是真,則為真,否則為假。

  • 3 = 0b 0000 0011
  • 5 = 0b 0000 0101
  • 3&5 = 0b 0000 0001

6. |

同理,和邏輯或運算 || 意思一樣,只不過作用在每一位上。

對於每一位來說,但凡有個真的就是真,否則為假。

  • 3 = 0b 0000 0011
  • 5 = 0b 0000 0101
  • 3|5 = 0b 0000 0111

7. ^

最後一個異或操作,相同為 0,不同為 1。

  • 3 = 0b 0000 0011
  • 5 = 0b 0000 0101
  • 3^5 = 0b 0000 0110

好啦,以上就是位運算的基本操作了,由這些運算做些巧妙的組合還可以得到很多有趣的結果,大家喜歡的話記得點贊評論哦~

對了,本週末新建了國內讀者交流群,想加入的小夥伴後臺回覆「進群」拉你進群呀~

另外 8 月自習室活動最後一週了,給我們自習室的小夥伴打起,應該有不少小夥伴能拿到齊姐的紅包了,還沒學夠 21 天的要繼續加油呀!

9月的自習室正在籌備中,如果你想參加,告訴我9月你想學習的天數和每天學習的時長,我們一起學習抱富!~

相關文章