位运算在项目中的妙用

前言

位运算是计算机科学中最基础、最高效的运算方式之一。虽然在实际开发中,位运算的使用场景相对较少,但在某些特定场景下,位运算能够带来显著的性能提升和代码简化。本文将深入探讨位运算的用法、常见应用场景,以及在实际项目和面试中的应用。

一、位运算基础

1.1 什么是位运算

位运算是直接对整数在内存中的二进制位进行操作的运算。JavaScript 中的位运算包括:

  • 按位与(&):两个位都为1时,结果才为1
  • 按位或(|):两个位有一个为1时,结果就为1
  • 按位异或(^):两个位不同时,结果为1
  • 按位非(~):对每一位取反
  • 左移(<<):将二进制位向左移动
  • 右移(>>):将二进制位向右移动(有符号)
  • 无符号右移(>>>):将二进制位向右移动(无符号)

1.2 JavaScript 中的位运算特点

需要注意的是,JavaScript 中的位运算会将操作数转换为 32 位有符号整数,运算后再转换回 JavaScript 数字。这意味着:

1
2
3
4
5
// JavaScript 中数字是 64 位浮点数
// 位运算时会转换为 32 位整数
console.log(0.5 | 0); // 0
console.log(1.5 | 0); // 1
console.log(-1.5 | 0); // -1

二、常用位运算技巧

2.1 快速取整

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 使用 | 0 或 ~~ 快速取整
console.log(3.14 | 0); // 3
console.log(~~3.14); // 3
console.log(3.14 >> 0); // 3
console.log(3.14 << 0); // 3

// 性能对比(在大量运算时,位运算更快)
const num = 123.456;
console.time('Math.floor');
for (let i = 0; i < 1000000; i++) Math.floor(num);
console.timeEnd('Math.floor'); // 约 5-10ms

console.time('bitwise');
for (let i = 0; i < 1000000; i++) num | 0;
console.timeEnd('bitwise'); // 约 1-3ms

2.2 判断奇偶性

1
2
3
4
5
6
7
8
9
// 使用 & 1 判断奇偶性
function isEven(n) {
return (n & 1) === 0;
}

console.log(isEven(2)); // true
console.log(isEven(3)); // false

// 比 n % 2 === 0 稍快

2.3 交换两个数(不使用临时变量)

1
2
3
4
5
6
7
8
9
// 使用异或运算交换两个数
let a = 5, b = 10;
a = a ^ b;
b = a ^ b;
a = a ^ b;
console.log(a, b); // 10, 5

// 或者使用解构(更推荐)
[a, b] = [b, a];

2.4 判断是否为 2 的幂

1
2
3
4
5
6
7
8
9
// 判断 n 是否为 2 的幂
function isPowerOfTwo(n) {
return n > 0 && (n & (n - 1)) === 0;
}

console.log(isPowerOfTwo(4)); // true
console.log(isPowerOfTwo(8)); // true
console.log(isPowerOfTwo(6)); // false
console.log(isPowerOfTwo(0)); // false

2.5 计算绝对值

1
2
3
4
5
6
7
8
// 使用位运算计算绝对值(仅适用于 32 位整数)
function abs(n) {
const mask = n >> 31;
return (n ^ mask) - mask;
}

console.log(abs(-5)); // 5
console.log(abs(5)); // 5

2.6 快速乘除 2 的幂次

1
2
3
4
5
6
7
8
9
// 左移相当于乘以 2 的 n 次方
console.log(5 << 1); // 10 (5 * 2)
console.log(5 << 2); // 20 (5 * 4)
console.log(5 << 3); // 40 (5 * 8)

// 右移相当于除以 2 的 n 次方(向下取整)
console.log(10 >> 1); // 5 (10 / 2)
console.log(10 >> 2); // 2 (10 / 4)
console.log(10 >> 3); // 1 (10 / 8)

三、位运算在权限系统中的应用

权限系统是位运算最经典的应用场景之一。通过位运算,我们可以用一个整数来表示多个权限,既节省空间,又提高查询效率。

3.1 权限系统设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
// 定义权限常量(使用 2 的幂次)
const PERMISSIONS = {
READ: 1, // 0001 (二进制)
WRITE: 2, // 0010 (二进制)
DELETE: 4, // 0100 (二进制)
ADMIN: 8, // 1000 (二进制)
};

// 或者使用左移运算符
const PERMISSIONS_V2 = {
READ: 1 << 0, // 1
WRITE: 1 << 1, // 2
DELETE: 1 << 2, // 4
ADMIN: 1 << 3, // 8
};

// 权限管理类
class PermissionManager {
constructor() {
this.permissions = 0; // 初始没有任何权限
}

// 添加权限(使用按位或)
addPermission(permission) {
this.permissions |= permission;
return this;
}

// 移除权限(使用按位与和按位非)
removePermission(permission) {
this.permissions &= ~permission;
return this;
}

// 检查是否有某个权限(使用按位与)
hasPermission(permission) {
return (this.permissions & permission) === permission;
}

// 检查是否有所有指定权限
hasAllPermissions(...permissions) {
const required = permissions.reduce((acc, p) => acc | p, 0);
return (this.permissions & required) === required;
}

// 检查是否有任意一个权限
hasAnyPermission(...permissions) {
return permissions.some(p => this.hasPermission(p));
}

// 切换权限(使用异或)
togglePermission(permission) {
this.permissions ^= permission;
return this;
}

// 获取所有权限(返回数组)
getAllPermissions() {
const result = [];
Object.keys(PERMISSIONS).forEach(key => {
if (this.hasPermission(PERMISSIONS[key])) {
result.push(key);
}
});
return result;
}

// 重置权限
reset() {
this.permissions = 0;
return this;
}
}

// 使用示例
const user = new PermissionManager();

// 添加权限
user.addPermission(PERMISSIONS.READ)
.addPermission(PERMISSIONS.WRITE);

console.log(user.hasPermission(PERMISSIONS.READ)); // true
console.log(user.hasPermission(PERMISSIONS.DELETE)); // false
console.log(user.hasAllPermissions(PERMISSIONS.READ, PERMISSIONS.WRITE)); // true

// 切换权限
user.togglePermission(PERMISSIONS.DELETE);
console.log(user.hasPermission(PERMISSIONS.DELETE)); // true

// 移除权限
user.removePermission(PERMISSIONS.READ);
console.log(user.hasPermission(PERMISSIONS.READ)); // false

console.log(user.getAllPermissions()); // ['WRITE', 'DELETE']

3.2 数据库中的权限存储

在实际项目中,权限通常存储在数据库中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 数据库表设计
// users 表
// id | name | permissions
// 1 | 张三 | 3 (二进制: 0011, 表示 READ + WRITE)
// 2 | 李四 | 7 (二进制: 0111, 表示 READ + WRITE + DELETE)
// 3 | 王五 | 15 (二进制: 1111, 表示所有权限)

// 后端 API 示例
function checkUserPermission(userId, requiredPermission) {
const user = getUserFromDB(userId);
return (user.permissions & requiredPermission) === requiredPermission;
}

// 中间件示例(Express.js)
function requirePermission(permission) {
return (req, res, next) => {
const user = req.user;
if ((user.permissions & permission) === permission) {
next();
} else {
res.status(403).json({ error: '权限不足' });
}
};
}

// 使用
app.get('/api/delete',
requirePermission(PERMISSIONS.DELETE),
(req, res) => {
// 删除逻辑
}
);

3.3 组合权限

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 定义组合权限
const PERMISSIONS = {
READ: 1,
WRITE: 2,
DELETE: 4,
ADMIN: 8,
};

// 组合权限:编辑者 = 读 + 写
const EDITOR = PERMISSIONS.READ | PERMISSIONS.WRITE;

// 组合权限:管理员 = 所有权限
const ADMIN = PERMISSIONS.READ | PERMISSIONS.WRITE | PERMISSIONS.DELETE | PERMISSIONS.ADMIN;

// 或者使用更简洁的方式
const ROLES = {
VIEWER: PERMISSIONS.READ,
EDITOR: PERMISSIONS.READ | PERMISSIONS.WRITE,
MODERATOR: PERMISSIONS.READ | PERMISSIONS.WRITE | PERMISSIONS.DELETE,
ADMIN: ~0, // 所有位都是 1,表示所有权限
};

四、开源代码库中的位运算应用

4.1 React 中的位运算

React 源码中大量使用了位运算来优化性能,特别是在 Fiber 架构中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// React 中的副作用标记(简化版)
const NoEffect = 0; // 0000
const PerformedWork = 1; // 0001
const Placement = 2; // 0010
const Update = 4; // 0100
const Deletion = 8; // 1000
const ContentReset = 16; // 10000
const Callback = 32; // 100000

// 组合副作用
const effect = Placement | Update; // 0010 | 0100 = 0110

// 检查副作用
if (effect & Placement) {
// 需要执行 Placement 操作
}

if (effect & Update) {
// 需要执行 Update 操作
}

// React 源码示例(简化)
function markEffect(fiber, effectTag) {
fiber.effectTag |= effectTag;
}

function hasEffect(fiber, effectTag) {
return (fiber.effectTag & effectTag) !== NoEffect;
}

4.2 Vue 3 中的位运算

Vue 3 在编译优化中也使用了位运算:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Vue 3 中的 PatchFlags(简化版)
const PatchFlags = {
TEXT: 1, // 0001
CLASS: 2, // 0010
STYLE: 4, // 0100
PROPS: 8, // 1000
FULL_PROPS: 16, // 10000
HYDRATE_EVENTS: 32, // 100000
STABLE_FRAGMENT: 64, // 1000000
KEYED_FRAGMENT: 128, // 10000000
UNKEYED_FRAGMENT: 256, // 100000000
NEED_PATCH: 512, // 1000000000
DYNAMIC_SLOTS: 1024, // 10000000000
DEV_ROOT_FRAGMENT: 2048, // 100000000000
HOISTED: -1, // 静态节点
BAIL: -2, // 需要完整 diff
};

// 组合标志
const flags = PatchFlags.TEXT | PatchFlags.CLASS; // 0001 | 0010 = 0011

// 检查标志
if (flags & PatchFlags.TEXT) {
// 需要更新文本
}

4.3 Lodash 中的位运算

Lodash 在内部实现中也使用了位运算来优化性能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Lodash 源码中的示例(简化)
// 使用位运算进行类型检查
const objectTag = '[object Object]';
const nullTag = '[object Null]';
const undefinedTag = '[object Undefined]';

// 使用位运算优化数组查找
function findIndex(array, predicate) {
const length = array == null ? 0 : array.length;
if (!length) return -1;

// 使用位运算优化循环
let index = -1;
while (++index < length) {
if (predicate(array[index], index, array)) {
return index;
}
}
return -1;
}

4.4 其他开源库中的应用

4.4.1 EventEmitter 中的事件类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// Node.js EventEmitter 的简化实现
class EventEmitter {
constructor() {
this.events = {};
this.maxListeners = 10;
}

// 使用位运算标记事件类型
const EVENT_TYPES = {
ONCE: 1, // 0001
PREPEND: 2, // 0010
SYNC: 4, // 0100
};

on(event, listener, flags = 0) {
if (!this.events[event]) {
this.events[event] = [];
}

const wrapper = {
listener,
flags,
once: (flags & EVENT_TYPES.ONCE) !== 0,
prepend: (flags & EVENT_TYPES.PREPEND) !== 0,
};

if (wrapper.prepend) {
this.events[event].unshift(wrapper);
} else {
this.events[event].push(wrapper);
}

return this;
}
}

4.4.2 状态机中的位运算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 状态机中使用位运算管理状态
const State = {
IDLE: 1, // 0001
LOADING: 2, // 0010
SUCCESS: 4, // 0100
ERROR: 8, // 1000
};

class StateMachine {
constructor() {
this.state = State.IDLE;
}

// 添加状态
addState(newState) {
this.state |= newState;
}

// 移除状态
removeState(state) {
this.state &= ~state;
}

// 检查状态
hasState(state) {
return (this.state & state) === state;
}

// 检查是否在加载中
isLoading() {
return this.hasState(State.LOADING);
}

// 检查是否成功
isSuccess() {
return this.hasState(State.SUCCESS) && !this.hasState(State.LOADING);
}
}

五、位运算在实际项目中的应用场景

5.1 功能开关(Feature Flags)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 使用位运算管理功能开关
const FEATURES = {
DARK_MODE: 1,
NOTIFICATIONS: 2,
ANALYTICS: 4,
PREMIUM: 8,
BETA_FEATURES: 16,
};

class FeatureManager {
constructor() {
this.enabledFeatures = 0;
}

enable(feature) {
this.enabledFeatures |= feature;
}

disable(feature) {
this.enabledFeatures &= ~feature;
}

isEnabled(feature) {
return (this.enabledFeatures & feature) === feature;
}

toggle(feature) {
this.enabledFeatures ^= feature;
}
}

// 使用
const features = new FeatureManager();
features.enable(FEATURES.DARK_MODE);
features.enable(FEATURES.NOTIFICATIONS);

if (features.isEnabled(FEATURES.DARK_MODE)) {
// 启用暗黑模式
}

5.2 数据压缩与编码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 使用位运算进行简单的数据压缩
class BitPacker {
// 将多个布尔值打包成一个数字
static pack(booleans) {
let result = 0;
for (let i = 0; i < booleans.length; i++) {
if (booleans[i]) {
result |= (1 << i);
}
}
return result;
}

// 从数字中解包布尔值
static unpack(value, length) {
const result = [];
for (let i = 0; i < length; i++) {
result.push((value & (1 << i)) !== 0);
}
return result;
}
}

// 使用示例
const flags = [true, false, true, true, false];
const packed = BitPacker.pack(flags);
console.log(packed); // 13 (二进制: 1101)

const unpacked = BitPacker.unpack(packed, 5);
console.log(unpacked); // [true, false, true, true, false]

5.3 颜色操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 使用位运算操作 RGB 颜色
class Color {
constructor(r, g, b, a = 255) {
this.r = r;
this.g = g;
this.b = b;
this.a = a;
}

// 将颜色打包成 32 位整数 (ARGB)
toInt32() {
return (this.a << 24) | (this.r << 16) | (this.g << 8) | this.b;
}

// 从 32 位整数解包颜色
static fromInt32(value) {
return new Color(
(value >> 16) & 0xFF, // R
(value >> 8) & 0xFF, // G
value & 0xFF, // B
(value >> 24) & 0xFF // A
);
}

// 提取单个颜色分量
static getRed(colorInt) {
return (colorInt >> 16) & 0xFF;
}

static getGreen(colorInt) {
return (colorInt >> 8) & 0xFF;
}

static getBlue(colorInt) {
return colorInt & 0xFF;
}

static getAlpha(colorInt) {
return (colorInt >> 24) & 0xFF;
}
}

// 使用示例
const color = new Color(255, 128, 64, 255);
const colorInt = color.toInt32();
console.log(colorInt.toString(16)); // ffff8040

const unpacked = Color.fromInt32(colorInt);
console.log(unpacked); // Color { r: 255, g: 128, b: 64, a: 255 }

5.4 布隆过滤器(简化版)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// 使用位运算实现简单的布隆过滤器
class SimpleBloomFilter {
constructor(size = 1024) {
this.size = size;
this.bitArray = new Array(Math.ceil(size / 32)).fill(0);
}

// 设置位
setBit(index) {
const arrayIndex = Math.floor(index / 32);
const bitIndex = index % 32;
this.bitArray[arrayIndex] |= (1 << bitIndex);
}

// 获取位
getBit(index) {
const arrayIndex = Math.floor(index / 32);
const bitIndex = index % 32;
return (this.bitArray[arrayIndex] & (1 << bitIndex)) !== 0;
}

// 添加元素
add(str) {
const hash1 = this.hash1(str);
const hash2 = this.hash2(str);
const hash3 = this.hash3(str);

this.setBit(hash1 % this.size);
this.setBit(hash2 % this.size);
this.setBit(hash3 % this.size);
}

// 检查元素是否存在
mightContain(str) {
const hash1 = this.hash1(str);
const hash2 = this.hash2(str);
const hash3 = this.hash3(str);

return this.getBit(hash1 % this.size) &&
this.getBit(hash2 % this.size) &&
this.getBit(hash3 % this.size);
}

// 简单的哈希函数
hash1(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = ((hash << 5) - hash) + str.charCodeAt(i);
hash = hash & hash; // 转换为 32 位整数
}
return Math.abs(hash);
}

hash2(str) {
let hash = 5381;
for (let i = 0; i < str.length; i++) {
hash = ((hash << 5) + hash) + str.charCodeAt(i);
}
return Math.abs(hash);
}

hash3(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = str.charCodeAt(i) + ((hash << 6) + (hash << 16) - hash);
}
return Math.abs(hash);
}
}

// 使用示例
const filter = new SimpleBloomFilter(1024);
filter.add('hello');
filter.add('world');

console.log(filter.mightContain('hello')); // true
console.log(filter.mightContain('world')); // true
console.log(filter.mightContain('test')); // false (可能)

六、面试中的高频位运算题目

6.1 只出现一次的数字

题目:给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现一次的元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 方法一:使用异或运算(最优解)
function singleNumber(nums) {
let result = 0;
for (let num of nums) {
result ^= num;
}
return result;
}

// 原理:a ^ a = 0, a ^ 0 = a
// 所有出现两次的数字异或后为 0,最后只剩下出现一次的数字

console.log(singleNumber([2, 2, 1])); // 1
console.log(singleNumber([4, 1, 2, 1, 2])); // 4

// 时间复杂度:O(n)
// 空间复杂度:O(1)

进阶:除了某个元素只出现一次以外,其余每个元素均出现三次。找出那个只出现一次的元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 方法:统计每一位上 1 的个数,对 3 取余
function singleNumber2(nums) {
let result = 0;
for (let i = 0; i < 32; i++) {
let count = 0;
for (let num of nums) {
if ((num >> i) & 1) {
count++;
}
}
if (count % 3 !== 0) {
result |= (1 << i);
}
}
return result;
}

console.log(singleNumber2([2, 2, 3, 2])); // 3
console.log(singleNumber2([0, 1, 0, 1, 0, 1, 99])); // 99

6.2 汉明距离

题目:两个整数之间的汉明距离指的是这两个数字对应二进制位不同的位置的数目。给出两个整数 x 和 y,计算它们之间的汉明距离。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function hammingDistance(x, y) {
let xor = x ^ y;
let distance = 0;

// 统计异或结果中 1 的个数
while (xor !== 0) {
distance++;
xor &= (xor - 1); // 清除最低位的 1
}

return distance;
}

// 或者使用内置方法
function hammingDistance2(x, y) {
return (x ^ y).toString(2).split('1').length - 1;
}

console.log(hammingDistance(1, 4)); // 2
// 1: 0 0 0 1
// 4: 0 1 0 0
// 0 1 0 1 (异或结果,有 2 个 1)

6.3 位 1 的个数

题目:编写一个函数,输入是一个无符号整数,返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为汉明重量)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 方法一:逐位检查
function hammingWeight(n) {
let count = 0;
while (n !== 0) {
if (n & 1) count++;
n >>>= 1; // 无符号右移
}
return count;
}

// 方法二:清除最低位的 1(更高效)
function hammingWeight2(n) {
let count = 0;
while (n !== 0) {
count++;
n &= (n - 1); // 清除最低位的 1
}
return count;
}

// 方法三:使用内置方法
function hammingWeight3(n) {
return n.toString(2).split('1').length - 1;
}

console.log(hammingWeight2(11)); // 3 (1011)
console.log(hammingWeight2(128)); // 1 (10000000)

6.4 颠倒二进制位

题目:颠倒给定的 32 位无符号整数的二进制位。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function reverseBits(n) {
let result = 0;
for (let i = 0; i < 32; i++) {
// 将 result 左移,为下一位腾出空间
result <<= 1;
// 获取 n 的最低位,加到 result 上
result |= (n & 1);
// n 右移,处理下一位
n >>>= 1;
}
return result >>> 0; // 转换为无符号整数
}

// 示例
// 输入: 00000010100101000001111010011100
// 输出: 00111001011110000010100101000000

console.log(reverseBits(43261596)); // 964176192

6.5 数字范围按位与

题目:给定范围 [m, n],其中 0 <= m <= n <= 2147483647,返回此范围内所有数字的按位与(包含 m, n 两端点)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function rangeBitwiseAnd(m, n) {
let shift = 0;
// 找到公共前缀
while (m < n) {
m >>= 1;
n >>= 1;
shift++;
}
return m << shift;
}

// 或者更直观的方法
function rangeBitwiseAnd2(m, n) {
while (m < n) {
n = n & (n - 1); // 清除 n 最低位的 1
}
return n;
}

console.log(rangeBitwiseAnd(5, 7)); // 4
// 5: 101
// 6: 110
// 7: 111
// 按位与: 100 = 4

6.6 2 的幂

题目:给定一个整数,编写一个函数来判断它是否是 2 的幂次方。

1
2
3
4
5
6
7
8
9
10
11
12
13
function isPowerOfTwo(n) {
if (n <= 0) return false;
return (n & (n - 1)) === 0;
}

// 原理:2 的幂次方在二进制中只有一个 1
// 例如:4 = 100, 8 = 1000
// n & (n - 1) 会清除最低位的 1
// 如果结果为 0,说明只有一个 1,即 2 的幂次方

console.log(isPowerOfTwo(1)); // true
console.log(isPowerOfTwo(16)); // true
console.log(isPowerOfTwo(218)); // false

6.7 4 的幂

题目:给定一个整数,编写一个函数来判断它是否是 4 的幂次方。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function isPowerOfFour(n) {
// 首先必须是 2 的幂
if (n <= 0 || (n & (n - 1)) !== 0) {
return false;
}
// 4 的幂次方在二进制中,1 的位置在奇数位(从右往左,从 0 开始)
// 例如:4 = 100 (第 2 位), 16 = 10000 (第 4 位)
// 与 0xAAAAAAAA (1010101010101010) 按位与应该为 0
return (n & 0xAAAAAAAA) === 0;
}

// 或者使用数学方法
function isPowerOfFour2(n) {
if (n <= 0) return false;
// 4 的幂次方对 3 取模等于 1
return (n & (n - 1)) === 0 && n % 3 === 1;
}

console.log(isPowerOfFour(16)); // true
console.log(isPowerOfFour(5)); // false
console.log(isPowerOfFour(1)); // true

6.8 缺失数字

题目:给定一个包含 0, 1, 2, ..., n 中 n 个数的序列,找出 0 .. n 中没有出现在序列中的那个数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 方法一:使用异或
function missingNumber(nums) {
let result = nums.length;
for (let i = 0; i < nums.length; i++) {
result ^= i ^ nums[i];
}
return result;
}

// 方法二:数学方法
function missingNumber2(nums) {
const n = nums.length;
const expectedSum = (n * (n + 1)) / 2;
const actualSum = nums.reduce((a, b) => a + b, 0);
return expectedSum - actualSum;
}

console.log(missingNumber([3, 0, 1])); // 2
console.log(missingNumber([9, 6, 4, 2, 3, 5, 7, 0, 1])); // 8

6.9 最大单词长度乘积

题目:给定一个字符串数组 words,找到 length(word[i]) * length(word[j]) 的最大值,并且这两个单词不含有公共字母。你可以认为每个单词只包含小写字母。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function maxProduct(words) {
const n = words.length;
const masks = new Array(n).fill(0);

// 使用位掩码表示每个单词包含的字母
for (let i = 0; i < n; i++) {
for (let char of words[i]) {
masks[i] |= (1 << (char.charCodeAt(0) - 'a'.charCodeAt(0)));
}
}

let max = 0;
for (let i = 0; i < n; i++) {
for (let j = i + 1; j < n; j++) {
// 如果没有公共字母(按位与为 0)
if ((masks[i] & masks[j]) === 0) {
max = Math.max(max, words[i].length * words[j].length);
}
}
}

return max;
}

console.log(maxProduct(["abcw", "baz", "foo", "bar", "xtfn", "abcdef"])); // 16

6.10 子集

题目:给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function subsets(nums) {
const result = [];
const n = nums.length;
const total = 1 << n; // 2^n 个子集

for (let i = 0; i < total; i++) {
const subset = [];
for (let j = 0; j < n; j++) {
// 检查第 j 位是否为 1
if ((i >> j) & 1) {
subset.push(nums[j]);
}
}
result.push(subset);
}

return result;
}

// 使用示例
console.log(subsets([1, 2, 3]));
// [[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]]

七、位运算的性能优化

7.1 性能对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 性能测试:取整操作
function performanceTest() {
const iterations = 10000000;
const num = 123.456;

// Math.floor
console.time('Math.floor');
for (let i = 0; i < iterations; i++) {
Math.floor(num);
}
console.timeEnd('Math.floor');

// 位运算
console.time('bitwise');
for (let i = 0; i < iterations; i++) {
num | 0;
}
console.timeEnd('bitwise');

// parseInt
console.time('parseInt');
for (let i = 0; i < iterations; i++) {
parseInt(num);
}
console.timeEnd('parseInt');
}

// 典型结果(可能因环境而异):
// Math.floor: ~50-100ms
// bitwise: ~10-30ms
// parseInt: ~200-400ms

7.2 何时使用位运算

适合使用位运算的场景:

  1. 权限管理系统
  2. 状态标志管理
  3. 大量数值计算(性能敏感)
  4. 数据压缩和编码
  5. 算法竞赛和面试

不适合使用位运算的场景:

  1. 代码可读性要求高的业务代码
  2. 需要处理大数(超过 32 位)
  3. 浮点数运算
  4. 团队协作中,其他成员不熟悉位运算

八、总结

位运算虽然看似简单,但在实际项目中有着广泛的应用:

  1. 权限系统:使用位运算可以高效地管理多权限
  2. 性能优化:在某些场景下,位运算比常规运算更快
  3. 状态管理:使用位掩码管理多个状态标志
  4. 算法实现:许多经典算法都依赖位运算
  5. 面试准备:位运算相关题目是算法面试的高频考点

虽然位运算能带来性能提升,但在实际开发中,我们应该在性能可读性之间找到平衡。对于大多数业务代码,可读性往往比微小的性能提升更重要。但在权限系统、状态管理等特定场景下,位运算是一个很好的选择。

参考资料


位运算在项目中的妙用
https://peterzhanghui.github.io/2025/12/20/位运算在项目中的妙用/
作者
前端嘉嘉
发布于
2025年12月20日
许可协议