位运算应用
位运算应用
1. 基础运算
1.1. 奇偶判断
真实世界中一般用求余2来判断奇偶, 转换成代码如下:
boolean isOdd(int num) {
return num % 2 == 1;
}
可以直接替换成位运算:
boolean isOdd(int num) {
return (num & 1) == 1;
}
奇数的最低位必然是1.
1.2. 统计1的数量
统计二进制数上有多少个1.
// openjdk17的Integer.bitCount
public static int bitCount(int i) {
// HD, Figure 5-2
i = i - ((i >>> 1) & 0x55555555);
i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
i = (i + (i >>> 4)) & 0x0f0f0f0f;
i = i + (i >>> 8);
i = i + (i >>> 16);
return i & 0x3f;
}
1.3. 取符号位
正数为1, 负数为-1, 0为0.
// 来自openjdk17的Integer.signum
public static int signum(int i) {
// HD, Section 2-7
return (i >> 31) | (-i >>> 31);
}
1.4. 大小写变换
小写转大写: ch |= 32, 大写转小写: ch &= ~32, 大写转小写, 小写转大写: ch ^= 32
1.5. 求数组中唯一一个出现次数为奇数的数字
数组中有一个数字只出现了奇数次, 其它数字都出现了偶数次, 可以通过异或找到这个数字.
利用异或的特性: a ^ a = 0, a ^ 0 = a
public int findOddTimeNum(int[] arr) {
var num = 0;
for(var i : arr) {
num ^= i;
}
return num;
}
2. 取大于等于正整数n的最小的2的整数次幂
例如, 3的最小2的整数次幂是4, 5的是8, 等等.
这个算法在jdk中的HashMap中有使用到.
实现方式至少有两种.
2.1. 高位右移取或
我们假设最高位的1在第k位上(从右往左数从1开始), k=28, 二进制如: 0b00001..., 这个最高位1的左侧全是0, 右侧位的值则不重要.
将其右移一位之后得到0b000001..., 两个值或之后得到: 0b000011..., 可以发现, 此时第28位与27位变成了1, 我们可以再右移两位进行同样的计算, 可以得到: 0b00001111..., 循环之前的做法, 最终我们能得到一个k右侧所有位都是1的数字, 即 2n - 1, 此时再加上1就得到了需要的数字.
因为int是32位数, 所以只需要分别执行移位1, 2, 4, 8, 16次即可.
注意如果一开始的数字就是2的整数幂, 我们期望得到他自己, 可以将其减一之后, 再进行运算.
代码如下:
int minPowerOf2(int num) {
--num;
num |= num >> 1;
num |= num >> 2;
num |= num >> 4;
num |= num >> 8;
num |= num >> 16;
return ++num;
}
考虑到溢出问题, 最好再规定一个最大值, 大于这个值的数可以抛出异常, 也可以使用该最大值返回.
static int MAX = 1 << 30;
int minPowerOf2(int num) {
if (num <= 0)
throw new IllegelArgumentException("must great than zero");
if(num >= MAX) {
return MAX;
}
--num;
num |= num >> 1;
num |= num >> 2;
num |= num >> 4;
num |= num >> 8;
num |= num >> 16;
return ++num;
}
2.2. 计算前缀0
统计前缀0的数量, 然后将-1(二进制所有位都是1)无符号右移前缀0的数量再加一.
统计前缀0的方法是二分处理, 此处不做分析, 参考文档: https://www.cnblogs.com/xiepl1997/p/13479769.html
static int MAX = 1 << 30;
public static int minPowerOf2(int num) {
if (num <= 0)
throw new IllegelArgumentException("must great than zero");
if(num >= MAX) {
return MAX;
}
int n = -1 >>> numberOfLeadingZeros(num - 1);
return n + 1;
}
// 来自openjdk17的Integer.numberOfLeadingZeros
public static int numberOfLeadingZeros(int i) {
if (i <= 0)
return i == 0 ? 32 : 0;
int n = 31;
if (i >= 1 << 16) { n -= 16; i >>>= 16; }
if (i >= 1 << 8) { n -= 8; i >>>= 8; }
if (i >= 1 << 4) { n -= 4; i >>>= 4; }
if (i >= 1 << 2) { n -= 2; i >>>= 2; }
return n - (i >>> 1);
}
3. 权限开关和标志位
使用位运算做权限有个经典的例子: linux系统的文件权限.
可执行, 可写, 可读的权限被分别设置为0b001, 0b010, 0b100即1, 2, 4, 由这几个数字的有无可以组成0-7内的任意一个数字, 共有八种权限组成.
标志位的逻辑与权限本质是一样的, 只是换了个名称.
一般代码类似下面这样:
int allPermission;
void addPerminssion(int permission) {
allPermission |= permission;
}
void removePermission(int permission) {
allPermission &= ~permission;
}
boolean hasPermission(int permission) {
return (allPermission & permission) == permission;
}
4. 异或加密
利用异或的性质: 明文 ^ 密钥 = 密文, 密文 ^ 密钥 = 明文
这种加密方式并不安全, 因为明文 ^ 密文 = 密钥, 但是便于实现且相当简单.
参考文档:
// 一个字节加密所有数据
public static void xorCipher(byte[] message, byte key) {
for(var i = 0; i < message.length; ++i) {
message[i] ^= key;
}
}
// 长度相当或key更长的加密
public static void xorCipher(byte[] message, byte[] key) {
if(message.length > key.length) {
throw new IllegelArgumentException("key长度不足");
}
for(var i = 0; i < message.length; ++i) {
message[i] ^= key[i];
}
}
5. 逻辑运算
假设1代表true, 0代表false, 可以使用位运算来代替逻辑运算符, 值得注意的是, 逻辑运算符可以立即返回但是位运算符不是.
在某些语言中, 位运算符也能作为逻辑运算符, 但是不具有"短路"特征, 例如java. 而对于某些语言, 可以同时表达位运算和逻辑运算, 例如javascript.
&的位运算与逻辑与&&相同.
|的位运算与逻辑或||相同.
expression ^ 1的位运算与!booleanExpression相同.
6. 版本号
通常人类友好阅读的版本格式采用三个数字, 以.分隔, 分别表示大版本(完全不兼容更新), 中版本(部分api变更或新增重要功能), 小版本(bug修复).
这种形式同样也可以采用位运算转换, 将这种字符串以数字进行保存.
例如, 将int32以8位分割, 高8位保留, 然后从高位到低位, 每8位分别表示大版本, 中版本, 小版本.
这样能每个版本能划分出256个版本, 对于大部分项目和框架都足以应付, 如果有需要, 可以灵活增加相应版本位数, 每增加1位, 版本号将增加一倍.
例如下面的代码实现了从x.y.z到对应数字版本的实现.
public Integer convertToDatabaseColumn(String attribute) {
if (attribute == null) {
return null;
}
String[] version = attribute.split("\\.");
return (Integer.parseInt(version[0]) << 16)
+ (Integer.parseInt(version[1]) << 8)
+ Integer.parseInt(version[2]);
}
public String convertToEntityAttribute(Integer dbData) {
if (dbData == null) {
return null;
}
return String.format(
"%d.%d.%d", (dbData >> 16) & 0xFF, (dbData >> 8) & 0xFF, dbData & 0xFF);
}

浙公网安备 33010602011771号