位运算简介
位运算符有&与|,用于数字间的运算,运算时将两个数字转为二进制,然后对每个同位进行运算得出0或1,最后将结果拼在一起得到最终结果
比如4 & 3,运算方式为:0b100 & 0b011 = 0b000,结果为0
一位表示有一种水果
用二进制数中的一位表示拥有一种水果,以四位为例,从左到右四位分别表示榴莲(durian)、樱桃(cherry)、香蕉(banana)和苹果(apple),有为1,没有为0,所以0b1111表示四种水果都有的状态,一个水果都没有的状态则用0b0000来表示
假装有人要去买水果
下面是一道题目:
假设有一位顾客小李要去买苹果、香蕉与樱桃与,那么可以用数字0b0111来表示购买水果的种类。
假设小李的老婆也去买水果,她买的是苹果、香蕉与榴莲,那么可以用数字0b1011来表示她购买水果的种类。
请求出小李和她老婆都买了哪个水果?
解答
求两者的并集可以用&运算符来计算,0b0111 & 0b1011的结果0b0011,作为人类,我们可以一眼就得出0b0011中有苹果和香蕉两种水果,那在程序中如何通过位运算来计算呢
将有某一种水果的状态用一个二进制数来表示,代码如下:
const apple = 0b0001;
const banana = 0b0010;
const cherry = 0b0100;
const durian = 0b1000;
两者都买的水果的值为0b0011,将这个值与有某种水果的二进制数值进行&位运算,如果结果为0b0000则表示没有,只要结果不为0b0000即表示有,最终代码如下:
const apple = 0b0001;
const banana = 0b0010;
const cherry = 0b0100;
const durian = 0b1000;
// 水果二进制值到名称
const fruitBinaryToName = {
[apple]: "苹果",
[banana]: "香蕉",
[cherry]: "樱桃",
[durian]: "榴莲",
};
const fruits = [apple, banana, cherry, durian];
// 小李买了苹果、香蕉和樱桃
const fruitsBuiedByLi = 0b0111;
// 小李妻子买了苹果、香蕉和榴莲
const fruitsBuiedByWife = 0b1011;
// 两者都买的水果
const fruitsBothBuied = fruitsBuiedByLi & fruitsBuiedByWife;
// 两者都买水果的名称
const namesBothBuied = [];
for (const fruit of fruits) {
// 如果求值不为0则表示有这种水果
if ((fruitsBothBuied & fruit) !== 0) {
namesBothBuied.push(fruitBinaryToName[fruit]);
}
}
console.log("两者都买的水果:", namesBothBuied);
分析
在上面这道题中,首先运用了位运算来求交集,即得出小李与妻子都买的水果。
之后又用位运算来判断是否有某种状态,即使用都买的水果与每个水果进行&运算,值不为0则表示有
上述题中如果要计算小李与妻子买的全部水果种类(并集),则可以用|运算来得出结果
添加与删除
使用上述所说方式还可以实现添加、删除状态(水果)
假设小李在结算的时候突然不要香蕉了,改为要买榴莲,那要如何计算小李最终购买水果的二进制值呢?
删除
不要香蕉,即要在小李所买水果值中删除香蕉,也就是0b0111中移除0b0010
计算方式为:先将香蕉的值0b0010取反得到0b1101,再与小李所买水果值0b0111进行&运算,得到的结果为0b0101,这个值表示苹果与樱桃,也就移除了香蕉,代码如下:
const apple = 0b0001;
const banana = 0b0010;
const cherry = 0b0100;
const durian = 0b1000;
// 小李买了苹果、香蕉和樱桃
const fruitsBuiedByLi = 0b0111;
// 小李所买水果中移除香蕉
const noBananaFruit = fruitsBuiedByLi & ~banana;
console.log(noBananaFruit.toString(2)); // 101
添加
计算添加榴莲则比较简单,只需要将榴莲并进来即可,并操作使用|运算符,即0b0101 | 0b1000,结果为0b1101,代码如下(接上面的代码):
// ... 省略删除中的代码
// 小李所买水果中添加榴莲
const addDurianFruit = noBananaFruit | durian;
console.log(addDurianFruit.toString(2)); // 1101
总结
使用二进制数的每一位来表示一种状态,则可以使用位运算对状态进行添加、删除,并可以判断是否含有某种状态。这种方式尤其适合计算量比较大的操作中,比如React源码中便采用了这种方式来标记需要对元素进行何种操作。