php 位运算 掩码,位掩码的使用

[时间]: 2019/10/15

[keyword]: 位掩码, BitMask

本文部分内容引用自互联网和 MDN, 前半部分会讲位运算, 后部分讲位掩码.

位运算

MDN 的文档写的太好了就直接拷贝过来了, 有兴趣的小伙伴位运算部分可以直接去看 MDN

位运算即对数字的二进制形式进行运算, JavaScript 内置的有:

运算符

用法

描述

a & b

对于每一个比特位,只有两个操作数相应的比特位都是1时,结果才为1,否则为0。

a | b

对于每一个比特位,当两个操作数相应的比特位至少有一个1时,结果为1,否则为0。

a ^ b

对于每一个比特位,当两个操作数相应的比特位有且只有一个1时,结果为1,否则为0。

~ a

反转操作数的比特位,即0变成1,1变成0。

a << b

将 a 的二进制形式向左移 b (< 32) 比特位,右边用0填充。

a >> b

将 a 的二进制表示向右移b(< 32) 位,丢弃被移出的位。

a >>> b

将 a 的二进制表示向右移b(< 32) 位,丢弃被移出的位,并使用 0 在左侧填充。

在一些其他的语言中, 例如 Haskell, 还内置了 rotate 操作.

示例中 1010, 1101 之类 看起来像二进制的均为二进制, 如果使用十进制会特殊说明

下方涉及到 1 0000 0101 之类的数字时, 1 为符号位

涉及到篇幅问题就不讲左移右移了, 有兴趣的小伙伴可以自己去查资料.

原码, 反码, 补码

在此之前我们需要了解一个概念: 机器数.

机器数是一个数的二进制形式在计算机中的表现形式, 他们分别是: 原码, 反码, 补码.

小伙伴看到这里可能会奇怪: "原码, 反码, 补码是什么, 为什么二进制要分成三种, 他们的表现形式又有什么不同"等种种疑惑.

首先我们都知道, 有整数和负数, 整数是很容易用二进制表示, 那负数呢? 计算机又看不懂 -, 于是科学家们又提出了有符号数这个概念, 用最高位来表示正负, 正数0, 负数1, 这种形式就叫原码.

// +2 的原码

0000 0010

// -2 的原码

1000 0010

复制代码

反码: 正数不变, 负数将原码形式除了符号位其余皆取反

// -2 的原码

1000 0010

// +2 的反码

1111 1101

复制代码

但是这两种形式运算都很麻烦啊, 于是就有了补码: 正数的补码就是其本身, 而负数的补码则是在其原码的基础上取反, 然后 + 1

// +2 的补码

0000 0010

// 原码负数 2

1111 1110

复制代码

使用补码形式计算机可以直接对数字进行 加 运算, 有兴趣的小伙伴可以继续在深入查资料.

相互转换:

十进制值

原码

反码

补码

105

0 0110 1001

0 0110 1001

0 0110 1001

-105

1 1110 1001

1 1001 0110

1 1001 0111

正数不变, 负数符号位为 1

正数不变, 负数为除了符号位皆取反

正数不变, 负数为除了符号位皆取反然后 + 1

按位非 ~

按位非是一个特殊的运算符, 因为它是单目运算符, 而其他都是双目运算符, 按位非对每个 bit 执行 ~ 操作, 即取反码, 流程如下:

将用于计算的值转为原码形式 : ~1 => 0 0001

按位取反 : 0001 => 1 1110

将反码转换成补码 : 1110 => 1 0010

转换十进制 : 1 0010 => -2

示例如下:

原码 反码 补码

~0 0001 => 1 1110 => 1 0010 => -2

等价于 ~1 => -2

相当于:

~x = -(x + 1)

复制代码

按位与 &

按位或对每个 bit 执行 & 操作, 如果相同位数的 bit 都为 1, 则结果为 1, 示例如下:

0001 & 1010 => 0000

等价于 1 & 10 => 0

0010 & 1010 => 0010

等价于 2 & 10 => 2

x & 0 = 0

1111 & 0000 => 0000

等价于 15 & 0 => 0

复制代码

按位或 |

按位或对每个 bit 执行 | 操作, 如果相同位数的 bit 有一个为 1, 则结果为 1, 示例如下:

0001 | 1010 => 1111

等价于 1 | 10 => 15

0010 | 1010 => 1010

等价于 2 | 10 => 10

x | 0 = x

1111 | 0000 => 1111

等价于 15 | 0 => 15

复制代码

按位异或 ^

按位异或对每个 bit 执行 ^ 操作, 如果相同位数的 bit 不相同, 且都不为 0, 则结果为 1, 示例如下:

0001 ^ 1010 => 1011

等价于 1 ^ 10 => 11

0010 ^ 1010 => 1000

等价于 2 ^ 10 => 8

x ^ 0 = x

0001 ^ 0000 => 0001

等价于 1 ^ 0 => 1

以及: 任何值同自身异或结果为 0

0001 ^ 0001 => 0000

1001 ^ 1001 => 0000

1111 ^ 1111 => 0000

复制代码

位掩码

上面终于讲完了位运算, 接着我们来请主角登场.

这个名词记得小伙伴们没有听过, 也可能已经用过了, linux 的权限系统就是使用位掩码来构建的, 当然可不要以为位掩码是一种技术, 它是一种运用位运算的技巧.

权限计算

我们在设计一个权限系统的时候往往免不了四种权限: 增删改查, 如果我们为每种有可能出现的权限都写一个变量来标识, 那我们需要写 2 ^ 4 = 16 个变量还储存这些组合.

这时就可以用位运算来优化, 首先我们用四个变量来表示权限:

const Insert = 1 // 0001

const Delete = 2 // 0010

const Update = 4 // 0100

const Select = 8 // 1000

复制代码

比如我们想给一个用户添加 Insert 权限, 那怎么添加呢?

只需要用 |运算符:

const user = registerUser(data)

// 初始为 0000

user.premission = 0

// 如果想新增一个权限 0000 | 0001 => 0001, 此时就拥有了 Insert 的权限

user.premission = user.premission | Insert

// 如果想在增加一个权限 0001 | 0010 => 0011, 此时就拥有了 Insert,Delete 权限

user.premission = user.premission | Delete

// 如果想在增加一个权限 0011 | 0100 => 0111, 此时就拥有了 Insert,Delete,Update 权限

user.premission = user.premission | Update

复制代码

此时肯定有小伙伴心里在想 0011 不是 3 吗, 我怎么知道 3 有没有包含 Insert 和 Delete 权限嘞?

只需要用 &运算符:

// 0011 & 0010 => 0010 也就是 Delete 的权限

user.premission & Delete === Delete

// 用户权限去 & 指定权限的结果等于指定权限, 那么就意味着该用户拥有该权限.

复制代码

在用户有 Delete 和 Insert 的同时, 我们怎么取出其中的一个

只需要用 ^运算符:

// 0011 ^ 0010 => 0001

user.premission ^ Delete === Insert

复制代码

如何删除权限?

需要用到 & 和 ~

// 有 Delete 和 Insert 权限

0011 & ~ 0001

↓ 取反 补码 十进制

1. 先做 ~ 0001, 这里我们只到补码部分 ~ 0 0001 => 1 1110 => 1 0010 => -2

2. 转换 0011 到补码形式 -> 0 0011

3. 0011 & 0010 => 0010 => 2

user.premission = user.premission & ~ Insert === Delete

复制代码

LoDash 的用法

这篇文章的初衷就是从阅读 lodash 源码衍生来的, 下面讲讲 lodash 中如何使用位掩码

在 lodash 的 cloneDeep 中用到了位掩码, lodash 的 baseClone 方法是诸多 clonexx 的基础, 那么 baseClone 势必要处理诸多状态, 例如是深克隆还是浅克隆, 克隆需不需要克隆 Symbol.

// 定义几种状态

const CLONE_DEEP_FLAG = 1

const CLONE_FLAT_FLAG = 2

const CLONE_SYMBOLS_FLAG = 4

function cloneDeep(value){

// cloneDeep 需要深克隆和克隆 Symbol

// 1 | 4 => 0001 | 0100 => 0101

return baseClone(value, CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG)

}

function baseClone(value, bitmask, customizer, key, object, stack){

// 0101 & 0001 => 0001 => 之后用的时候 if(isDeep) 会隐式转换成 true

const isDeep = bitmask & CLONE_DEEP_FLAG

// 0101 & 0010 => 0000 => 之后用的时候 if(isFlat) 会隐式转换成 false

const isFlat = bitmask & CLONE_FLAT_FLAG

// 0101 & 0100 => 0100 => 之后用的时候 if(isFull) 会隐式转换成 true

const isFull = bitmask & CLONE_SYMBOLS_FLAG

}

复制代码

课外练习, 使用位运算的一些技巧

原地交换值

其技巧是利用

x = x ^ y

y = (x ^ y) ^ y

y = x ^ (y ^ y) : y ^ y = 0, x ^ 0 = x, 所以 y = x (参考按位异或篇提到的)

接下来算 x

x = (x ^ y) ^ (y ^ x ^ y)

x = x ^ y ^ y ^ x ^ y

x = y

let x = 2

let y = 3

// 0010 ^ 0011 => 0001 => 1

x = x ^ y

// 0011 ^ 0001 => 0010 => 2

y = y ^ x

// 0001 ^ 0010 => 0011 => 3

x = x ^ y

复制代码

判断奇偶

其技巧就是判断二进制的最低位是不是 1

x = 2

// 为真则是偶数, 为假则是单数

x & 1 === 0

// 0010 & 0001 => 0000 => 0, x 是 偶数

x & 1

// x 赋值为 3

x = 3

// 0011 & 0001 => 0001 => 1, x 不是偶数

x & 1

复制代码

交换符号

将 x 变成 -x, -x 变成 x

~ (x) + 1

复制代码

笔者比较愚钝, 好多次才明白其中的意义, 本文主要是将其记录下来希望能给小伙伴们一些启发, 如有错误欢迎提 issue.

没有感情结束语: 本文同样发于个人博客

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值