位運算的方法在其它語言也是同樣的,不侷限於JS,因此本文提到的位運算也適用於其它語言。javascript
位運算是低級的運算操做,因此速度每每也是最快的(相對其它運算如加減乘除來講),而且藉助位運算的特性還能實現一些算法。恰當地使用運算有不少好處。下面舉幾個例子。java
這是一個很經常使用的技巧,如判斷一個數是否在數組裏面:算法
// 若是url含有?號,則後面拼上&符號,不然加上?號
url += ~url.indexOf("?") ? "&" : "?";複製代碼
由於:數組
~-1 === 0bash
-1在內存的表示的二進制符號全爲1,按位非以後就變成了0. 進一步說明——1在內存的表示爲: 0000...0001,第一位0表示符號位爲正,若是是-1的話符號位爲負用1表示1000...0001,這個是-1的原碼,而後符號位不動,其他位取反變成1111...1110,這個就是-1的反碼錶示,反碼再加1就變成了1111...1111,這個就是-1的補碼,負數在內存裏面(機器數)使用補碼錶示,正數是用原碼。因此所有都是1的機器數按位非以後就變成了全爲0。剩下的其它全部數按位非都不爲0,因此利用這個特性能夠用來作indexOf的判斷,這樣代碼看起來更簡潔一點。網絡
交換兩個整數的值,最直觀的作法是藉助一個臨時變量:數據結構
let a = 5,
b = 6;
// 交換a, b的值
let c = a;
a = b;
b = c;複製代碼
如今要求不能使用額外的變量或內容空間來交換兩個整數的值。這個時候就得藉助位運算,使用異或能夠達到這個目的:post
let a = 5,
b = 6;
a = a ^ b;
b = a ^ b; // b 等於 5
a = a ^ b; // a 等於 6複製代碼
這個是爲何呢?很簡單,把一、2式:ui
a = a ^ b;
b = a ^ b;複製代碼
連起來就等價於:google
b = (a ^ b) ^ b = a ^ (b ^ b) = a ^ 0 = a;複製代碼
同理連同第3式可得:
a = (a ^ b) ^ a // 在執行第3式的時候b已經變成a了,而a是第1式的a ^ b
= a ^ a ^ b = 0 ^ b = b;複製代碼
爲何a ^ a = 0, b ^ b = 0呢?由於異或的運算就是這樣的。以下示例:
01011010
^ 10010110
-----------
11001100複製代碼
異或的運算過程能夠看成把兩個數加起來,而後進位去掉,0 + 0 = 0,1 + 0 = 1,1 + 1 = 0。這樣就很好記。因此a ^ a在全部二進制位上,要麼同爲0,要麼同爲1,相加結果都爲0,最後就爲0.
異或還常常被用於加密。
按位與有不少做用,其中一個就是去操做數的高位,只保留低位,例若有a, b兩個數:
let a = 0b01000110; // 十進制爲70
let b = 0b10000101; // 十進制爲133複製代碼
如今認爲他們的高位是沒用的,只有低4位是有用的,即最後面4位,爲了比較a,b後4位的大小,能夠這樣比較:
a & 0b00001111 < b & 0b00001111 // true複製代碼
a, b的前4位和0000與一下以後就都變成0了,然後四位和1111與一下以後仍是原來的數。這個實際的做用是有一個數字它的前幾位被看成A用途,然後幾位被用當B用途,爲了去掉前幾位對B用途的影響,就能夠這樣與一下。
另一個例子是子網掩碼,假設如今我是網絡管理員,我可以管理的IP地址是從192.168.1.0到192.168.1.255,即只能調配最後面8位。如今把這些IP地址分紅6個子網,經過IP地址進行區分,因爲6等於二進制的110,因此最後面8位的前3位用來表示子網,然後5位用來表示主機(即總的主機數的範圍爲00001 ~ 11111, 共30個)。當前網絡的子網掩碼取爲255.255.255.111 00000即255.255.255.224,假設某臺主機的IP地址爲192.168.1.120,如今要知道它處於哪一個子網,能夠用它IP地址與子網掩碼與一下:120 & 224 = 96 = 0b 011 00000,就知道它所在的子網爲011即3號子網。
這個是保留高位去掉低位的例子,恰好與上面的例子相反。
如今有個後臺管理系統,操做權限分爲一級、二級、三級管理員,其中一級管理員擁有最高的權限,2、三級較低,有些操做只容許1、二級管理員操做,有些操做只容許1、三級管理員操做。如今已經登錄的某權限的用戶要進行某個操做,要用怎樣的數據結構能很方便地判斷他能不能進行這個操做呢?
咱們用位來表示管理權限,一級用第3位,二級用第2位,三級用第1位,即一級的權限表示爲0b100 = 4,二級權限表示爲0b010 = 2,三級權限表示爲0b001 = 1。若是A操做只能由一級和二級操做,那麼這個權限值表示爲6 = 0b110,它和一級權限與一下:6 & 4 = 0b110 & 0b100 = 4,獲得的值不爲0,因此認爲有權限,同理和二級權限與一下6 & 2 = 2也不爲0,而與三級權限與一下6 & 1 = 0,因此三級沒有權限。這裏標誌位的1表示打開,0表示關閉。
這樣的好處在於,咱們能夠用一個數字,而不是一個數組來表示某個操做的權限集,同時在進行權限判斷的時候也很方便。
上面構造了一個權限的屬性集,屬性集的例子還有不少,例如我在《Google地圖開發總結》裏面就提到一個邊界判斷的例子——要在當前鼠標的位置往上彈一個懸浮框,以下圖左所示,可是當鼠標比較靠邊的時候就會致使懸浮框超出邊界了,以下圖右所示:
爲此,須要作邊界判斷,總共有3種超出狀況:右、上、左,而且可能會疊加,如鼠標在左上角的時候會致使左邊和上面同時超出。須要記錄超出的狀況進行調整,用001表示右邊超出,010表示上方超出,100表示左邊超出,以下代碼計算:
let postFlag = 0;
//右邊超出
if(pos.right < maxLen) posFlag |= 1;
//上面超出
if(pos.top < maxLen) posFlag |= 2;
//左邊超出
if(pos.left < maxLeftLen) posFlag |= 4;
//對超出的狀況進行處理,代碼略
switch(posFlag){
case 1: //右
case 2: //上
case 3: //右上
case 4: //左
case 6: //左上
}複製代碼
若是左邊和上面同時超出,那麼經過或運算2 | 4 = 6,獲得6 = 0b110. 就知道了超出的狀況,這樣的代碼相對於在if裏面寫兩個判斷要好一些。
這裏有個例子——不使用加減乘除來作加法,常常用來考察對位運算的掌握狀況。讀者能夠先自行嘗試分析和實現。
不能用加減乘除,意思就是要你用位運算進行計算。以實際例子說明,如a = 81 = 0b1010001,b = 53 = 0b0110101。經過異或運算,咱們發現異或把兩個數相加可是不能進位,而經過與運算可以知道哪些位須要進位,以下所示:
1010001
^ 0110101
---------
1100100
1010001
& 0110101
---------
0010001複製代碼
把經過與運算獲得的值向左移一位,再和經過異或獲得的值相加,就至關於實現了進位,這個應該不難理解。爲了實現這兩個數的相加能夠再重複這個過程:先異或,再與,而後進位,直到不須要再進位了就加完了。因此不難寫出如下代碼:
function addByBit(a, b) {
if (b === 0) {
return a;
}
// 不用進位的相加
let c = a ^ b;
// 記錄須要進位的
let d = a & b;
d = d << 1;
// 繼續相加,直到d進位爲0
return addByBit(c, d);
}
let ans = addByBit(5, 8);
console.log(ans);複製代碼
位運算還常常用於生成隨機數、哈希,例如Chrome對字符串進行哈希的算法是這樣的:
uint32_t StringHasher::AddCharacterCore(uint32_t running_hash, uint16_t c) {
running_hash += c;
running_hash += (running_hash << 10);
running_hash ^= (running_hash >> 6);
return running_hash;
}複製代碼
不斷對當前字符串的ASCII值進行累加運算,裏面用到了異或,左移和右移。
本篇介紹了使用位運算的幾個實際的例子,但願能加深位運算的理解、對開發有所幫助。