经常用bitCount,也得来个源码解析

bitCount源码解析

因为是在idea里面写的文章,格式化的时候 用 ```java 标签写内容 ,格式化会把缩进搞没了,还是用 ```text标签
每组xx bit,意思是例如每组8bit , 代表一组里面左右部分都是4bit . 要合并左右部分的bit1位数的意思

源码

public static int bitCount(int i){
        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;
}

代码的思路

最直观的思路就是第二行 , 也就是每组4bit , 合并左右两个bit位的bit1位数时 .
代码是:


i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);

例如有每组2n个bit , 要把 左边和右边的bit1位数相加 . 那么就是 (i & mask) + (i >>> n) & mask .
也就是源代码变成这样也是可以得出结果的


int[] arr = new int[]{0x55555555, 0x33333333, 0x0f0f0f0f, 0x00ff00ff, 0x0000ffff};
for (int b = 0; b < 5; b++) {
    i = (i & arr[b]) + ((i >>> (1 << b)) & arr[b]);
}
return i & 0x3f;

或者直观一点 , 把需要位移的位数弄成变量 , 更直观 , 当每组左右部分都是16bit的时候 , 这轮运算完毕就可以 & 0x3f 返回结果了


int[] arr = new int[]{0x55555555, 0x33333333, 0x0f0f0f0f, 0x00ff00ff, 0x0000ffff};
for (int a = 1, b = 0; a <= 16; a <<= 1, b++) {
    i = (i & arr[b]) + ((i >>> a) & arr[b]);
}
return i & 0x3f;  

简单举例


例如到了第二步了 , 10001110
mask是 0x33333333 , 也就是 001100110011......
每组4bit,要合并左右两个bit的bit位数 , (i & mask) + (i >>> n) & mask 格式 .
i&mask , 先取出右侧的位数 也就是 0000 0010 ,
 用 (i >>> n) & mask 取出左侧位数 也就是先右移两位在 & mask , 就是 0010 0011
 相加之后 就是 0010 0101 . 
例如右边那组 , 原来是 11 10 , 也就是左侧3个1 , 右侧2个1 ,合并之后 , 总共 101 , 也就是5个1 . 

官方的优化

会发现,源码里面除了第二行,全都不是标准的 (i & mask) + (i >>> n) & mask
所以除了每组4bit,也就是第二行的操作 , 其他的都是在优化

public static int bitCount(int i){
    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;
}

第一行的优化 , 也就是每组2bit,第一次运算的时候 , 稍稍特殊一些

首先忽略奇数位(当奇数位都是0) , 如果单纯只想算偶数位的1的个数 .
直接 (i & 0xaaaaaaaa) - ((i >>> 1) & 0x55555555); 就行 (a就是1010)
其实只有两种情况 ,

  • 例如10 , 右侧是01 . 减一减就是偶数位bit1个数 , 也就是1 .
  • 00 - 00 = 0 , bit位个数也是0 , 正确.

奇数部分本身就是低位 , 做 i & 0x55555555 的操作 ,这俩加一加,可以得出每两位的bit1位数
(i & 0xaaaaaaaa) - ((i >>> 1) & 0x55555555) + i & 0x55555555.
(i & 0xaaaaaaaa) 和 i & 0x55555555 合并一下 . 那就是i本身了 , 所以最终是 i - ((i >>> 1) & 0x55555555);
这样是比 (i & mask) + (i >>> n) & mask 的这种运算减少一点压力的

第三行的优化,每组8bit的优化

第三行是每组8bit . 因为在4bit那组 . 确定了每组最多是4个 , 也就是100个
那么最大是 0100 + 0100 = 1000 . 是超不过4个bit的 .
所以 第三行的 原本应该是(i & 0x0f0f0f0f) + ((i >>> 4) & 0x0f0f0f0f)
实际上可以先加再&mask 也就是 (i + (i >>> 4)) & 0x0f0f0f0f

每组16和32bit的优化

到了底下的每组8 或者16bit(也就是合并左右8bit的bit1位数) 这里完全不用&mask了 .
因为 最后两行相加左右两部分的bit位数 , 最多也就是16和32个,32也才100000 , 最多也只占5和6个bit .
高位不削除 , 让他留着也无所谓 , 最后只取末尾6bit就好.
(因为第四步没削高位 , 第9位到第14位其实也有值 ,也就是右往左数第二组8bit的后6位 ,但是不管他就好)
最后 & 0x3f (其实 &0xff也是一样的,只不过右往左数第7,8位必定是0) , 取出最后的6个bit位 , 就是最后的结果:bit1位数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值