前言 位运算是计算机科学中最基础、最高效的运算方式之一。虽然在实际开发中,位运算的使用场景相对较少,但在某些特定场景下,位运算能够带来显著的性能提升和代码简化。本文将深入探讨位运算的用法、常见应用场景,以及在实际项目和面试中的应用。
一、位运算基础 1.1 什么是位运算 位运算是直接对整数在内存中的二进制位进行操作的运算。JavaScript 中的位运算包括:
按位与(&) :两个位都为1时,结果才为1
按位或(|) :两个位有一个为1时,结果就为1
按位异或(^) :两个位不同时,结果为1
按位非(~) :对每一位取反
左移(<<) :将二进制位向左移动
右移(>>) :将二进制位向右移动(有符号)
无符号右移(>>>) :将二进制位向右移动(无符号)
1.2 JavaScript 中的位运算特点 需要注意的是,JavaScript 中的位运算会将操作数转换为 32 位有符号整数,运算后再转换回 JavaScript 数字。这意味着:
1 2 3 4 5 console .log (0.5 | 0 ); console .log (1.5 | 0 ); console .log (-1.5 | 0 );
二、常用位运算技巧 2.1 快速取整 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 console .log (3.14 | 0 ); console .log (~~3.14 ); console .log (3.14 >> 0 ); console .log (3.14 << 0 ); const num = 123.456 ;console .time ('Math.floor' );for (let i = 0 ; i < 1000000 ; i++) Math .floor (num);console .timeEnd ('Math.floor' ); console .time ('bitwise' );for (let i = 0 ; i < 1000000 ; i++) num | 0 ;console .timeEnd ('bitwise' );
2.2 判断奇偶性 1 2 3 4 5 6 7 8 9 function isEven (n ) { return (n & 1 ) === 0 ; }console .log (isEven (2 )); console .log (isEven (3 ));
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); [a, b] = [b, a];
2.4 判断是否为 2 的幂 1 2 3 4 5 6 7 8 9 function isPowerOfTwo (n ) { return n > 0 && (n & (n - 1 )) === 0 ; }console .log (isPowerOfTwo (4 )); console .log (isPowerOfTwo (8 )); console .log (isPowerOfTwo (6 )); console .log (isPowerOfTwo (0 ));
2.5 计算绝对值 1 2 3 4 5 6 7 8 function abs (n ) { const mask = n >> 31 ; return (n ^ mask) - mask; }console .log (abs (-5 )); console .log (abs (5 ));
2.6 快速乘除 2 的幂次 1 2 3 4 5 6 7 8 9 console .log (5 << 1 ); console .log (5 << 2 ); console .log (5 << 3 ); console .log (10 >> 1 ); console .log (10 >> 2 ); console .log (10 >> 3 );
三、位运算在权限系统中的应用 权限系统是位运算最经典的应用场景之一。通过位运算,我们可以用一个整数来表示多个权限,既节省空间,又提高查询效率。
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 const PERMISSIONS = { READ : 1 , WRITE : 2 , DELETE : 4 , ADMIN : 8 , };const PERMISSIONS_V2 = { READ : 1 << 0 , WRITE : 1 << 1 , DELETE : 1 << 2 , ADMIN : 1 << 3 , };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 )); console .log (user.hasPermission (PERMISSIONS .DELETE )); console .log (user.hasAllPermissions (PERMISSIONS .READ , PERMISSIONS .WRITE )); user.togglePermission (PERMISSIONS .DELETE );console .log (user.hasPermission (PERMISSIONS .DELETE )); user.removePermission (PERMISSIONS .READ );console .log (user.hasPermission (PERMISSIONS .READ )); console .log (user.getAllPermissions ());
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 function checkUserPermission (userId, requiredPermission ) { const user = getUserFromDB (userId); return (user.permissions & requiredPermission) === requiredPermission; }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 , };
四、开源代码库中的位运算应用 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 const NoEffect = 0 ; const PerformedWork = 1 ; const Placement = 2 ; const Update = 4 ; const Deletion = 8 ; const ContentReset = 16 ; const Callback = 32 ; const effect = Placement | Update ; if (effect & Placement ) { }if (effect & Update ) { }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 const PatchFlags = { TEXT : 1 , CLASS : 2 , STYLE : 4 , PROPS : 8 , FULL_PROPS : 16 , HYDRATE_EVENTS : 32 , STABLE_FRAGMENT : 64 , KEYED_FRAGMENT : 128 , UNKEYED_FRAGMENT : 256 , NEED_PATCH : 512 , DYNAMIC_SLOTS : 1024 , DEV_ROOT_FRAGMENT : 2048 , HOISTED : -1 , BAIL : -2 , };const flags = PatchFlags .TEXT | PatchFlags .CLASS ; 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 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 class EventEmitter { constructor ( ) { this .events = {}; this .maxListeners = 10 ; } const EVENT_TYPES = { ONCE : 1 , PREPEND : 2 , SYNC : 4 , }; 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 , LOADING : 2 , SUCCESS : 4 , ERROR : 8 , };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); const unpacked = BitPacker .unpack (packed, 5 );console .log (unpacked);
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 class Color { constructor (r, g, b, a = 255 ) { this .r = r; this .g = g; this .b = b; this .a = a; } toInt32 ( ) { return (this .a << 24 ) | (this .r << 16 ) | (this .g << 8 ) | this .b ; } static fromInt32 (value ) { return new Color ( (value >> 16 ) & 0xFF , (value >> 8 ) & 0xFF , value & 0xFF , (value >> 24 ) & 0xFF ); } 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 )); const unpacked = Color .fromInt32 (colorInt);console .log (unpacked);
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; } 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' )); console .log (filter.mightContain ('world' )); console .log (filter.mightContain ('test' ));
六、面试中的高频位运算题目 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; }console .log (singleNumber ([2 , 2 , 1 ])); console .log (singleNumber ([4 , 1 , 2 , 1 , 2 ]));
进阶 :除了某个元素只出现一次以外,其余每个元素均出现三次。找出那个只出现一次的元素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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 ])); console .log (singleNumber2 ([0 , 1 , 0 , 1 , 0 , 1 , 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 ; while (xor !== 0 ) { distance++; xor &= (xor - 1 ); } return distance; }function hammingDistance2 (x, y ) { return (x ^ y).toString (2 ).split ('1' ).length - 1 ; }console .log (hammingDistance (1 , 4 ));
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; }function hammingWeight2 (n ) { let count = 0 ; while (n !== 0 ) { count++; n &= (n - 1 ); } return count; }function hammingWeight3 (n ) { return n.toString (2 ).split ('1' ).length - 1 ; }console .log (hammingWeight2 (11 )); console .log (hammingWeight2 (128 ));
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 <<= 1 ; result |= (n & 1 ); n >>>= 1 ; } return result >>> 0 ; }console .log (reverseBits (43261596 ));
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 ); } return n; }console .log (rangeBitwiseAnd (5 , 7 ));
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 ; }console .log (isPowerOfTwo (1 )); console .log (isPowerOfTwo (16 )); console .log (isPowerOfTwo (218 ));
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 ) { if (n <= 0 || (n & (n - 1 )) !== 0 ) { return false ; } return (n & 0xAAAAAAAA ) === 0 ; }function isPowerOfFour2 (n ) { if (n <= 0 ) return false ; return (n & (n - 1 )) === 0 && n % 3 === 1 ; }console .log (isPowerOfFour (16 )); console .log (isPowerOfFour (5 )); console .log (isPowerOfFour (1 ));
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 ])); console .log (missingNumber ([9 , 6 , 4 , 2 , 3 , 5 , 7 , 0 , 1 ]));
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++) { 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" ]));
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; for (let i = 0 ; i < total; i++) { const subset = []; for (let j = 0 ; j < n; j++) { if ((i >> j) & 1 ) { subset.push (nums[j]); } } result.push (subset); } return result; }console .log (subsets ([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 ; 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' ); console .time ('parseInt' ); for (let i = 0 ; i < iterations; i++) { parseInt (num); } console .timeEnd ('parseInt' ); }
7.2 何时使用位运算 适合使用位运算的场景:
权限管理系统
状态标志管理
大量数值计算(性能敏感)
数据压缩和编码
算法竞赛和面试
不适合使用位运算的场景:
代码可读性要求高的业务代码
需要处理大数(超过 32 位)
浮点数运算
团队协作中,其他成员不熟悉位运算
八、总结 位运算虽然看似简单,但在实际项目中有着广泛的应用:
权限系统 :使用位运算可以高效地管理多权限
性能优化 :在某些场景下,位运算比常规运算更快
状态管理 :使用位掩码管理多个状态标志
算法实现 :许多经典算法都依赖位运算
面试准备 :位运算相关题目是算法面试的高频考点
虽然位运算能带来性能提升,但在实际开发中,我们应该在性能 和可读性 之间找到平衡。对于大多数业务代码,可读性往往比微小的性能提升更重要。但在权限系统、状态管理等特定场景下,位运算是一个很好的选择。
参考资料