首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >汉明重量: 统计二进制数中1的个数与JDK中的设计实现

汉明重量: 统计二进制数中1的个数与JDK中的设计实现

作者头像
一个架构师
发布2022-06-20 20:00:32
发布2022-06-20 20:00:32
5280
举报

Redis位图文章中,曾说过利用位图做登录统计,今天就来看下是如何实现统计功能的, JDK中又是如何设计实现的.

先说明下统计要求:

统计一个数字其二进制表达式中数字位数为1(或者说非0) 的个数.

这种统计也叫汉明重量(Hamming weight).

1. 利用位的与计算做统计

利用位的与操作, 判断某一位是否为1;

代码语言:javascript
复制
1 & 1 = 1
1 & 1 = 0

整个流程如下: 判断数字n右数第一位是否为1,并计数;

同时将数字n右移1位, 并重复上述过程,直到数字n为0.

缺陷: 这种只适合计算大于0的数字, 因为小于0的数字,高位为1, 在右移的过程中,使每一位都变成了1(0xFFFFFFFF), 无法正确计算.

代码如下:

代码语言:javascript
复制
public static int bitCount(int n) {
int count = 0;
while (n > 0) {
if ((n & 1) == 1) {
            count++;
        }
        n = n >> 1;
    }
return count;
}

2. 优化后的与运算

为了解决负数的问题,需要对上述方式做些调整.

待统计的数字n保持不变, 将与其进行位操作的flag进行左移操作, 再进行比较统计.

代码如下:

代码语言:javascript
复制
public static int countOfBit1(int n) {
int count = 0;
int flag = 1;
while (flag != 0) {
if ((n & flag) != 0) {
            count++;
        }
        flag = flag << 1;
    }
return count;
}

3. JDK实现的位统计

在JDK中的Integer类中也有位统计的具体实现.

代码语言:javascript
复制
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;
}

这段代码看起来很复杂, 但原理是比较简单的,

先2个一组, 求二进制1的个数, 并且用两位二进制存储在原处, 然后4个一组, 求二进制位1的个数, 再把它存储以4位二进制到原处, 以此类推, 再8个一组, 16个一组统计个数.

下面在看下代码实现过程:

1. 两位一组,统计1的个数

先看下数据i与统计值c的关系

二进制数i

位值为1的统计数c

统计值c的二进制表达式

统计值c与原数据i的关系

00

0

00

00 = 00 - 00

01

1

01

01 = 01 - 00

10

1

01

01 = 10 - 01

11

2

10

10 = 11 - 01

通过统计值c与原数据i的关系, 可以发现减数= i >>>1

所以统计值 c = i - ( i >>> 1)

值得注意的是, 右移之后高位一定是0

那多余2位时,右移会产生一个问题: 高位部分右移的数据会影响低位计算.

例如: 二进制数i = 0110

根据表格和统计值表达式,预期结果为: 0001

实际右移结果: 0011

可以发现左数第二位’1’是从高位右移下来的,影响了预期结果, 为消除影响需处理掉右移下来的高位

处理方式: 对高位进行与01操作

整体表达式为: 0110 >>> 1 & 0101 = 0001

每2位一组的统计结果为:

代码语言:javascript
复制
c = i -(i >>> 1 & 0101) 
= i -(i >>> 1 & 0x5) 
= 0110 - 0001 
= 0101

高两位统计结果为1,低两位统计结果也为1,结果正确

所以Integer的每2位一组的统计值为

代码语言:javascript
复制
i - ((i >>> 1) & 0x55555555);

2. 每4位一组统计1的个数

在上面第一步的前提下, 将统计好的2位一组的数量,合并相加.

同样以二进制数据i = 0110 为例,

经过两位一组的统计后为: c = 0101

四为一组相加的结果为: 01 + 01 = 10

过程如下:

高位右移2位: 0101 >> 2 = 0001

低位去掉高位统计干扰,只保留低位: 0101 & 0011 = 0001

统计结果:

高位个数+低位个数= (0101 >> 2 ) + (0101 & 0011) =(0101 >> 2 ) + (0101 & 0x3) = 0001 + 0001 = 0010

需注意的是, Integer是32位, 在高位右移之后同样也排除来自更高位的干扰

正确的处理过程为:

代码语言:javascript
复制
高位个数+低位个数
= (0101 >> 2 & 0011) + (0101 & 0011) 
= (0101 >> 2 & 0x3 ) + (0101 & 0x3) 
= 0001 + 0001 
= 0010

所以,每4位一组统计过程为

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

3. 每8位一组,统计1的个数

操作过程与上述步骤类似,每2个相邻的4位一组数据相加,并排除高位干扰.

即: i = (i + (i >>> 4)) & 0x0f0f0f0f;例如,经上述步骤计算后

代码语言:javascript
复制
                       i = 0010 0010 0010 0010
                  i>>> 4 = 0000 0010 0010 0010
              i +(i>>>4) = 0010 0100 0100 0100
                  0x0f0f = 0000 1111 0000 1111
(i + (i >>> 4)) & 0x0f0f = 0000 0100 0000 0100

与预期相同

4. 每16位一组统计与32位一组统计

具体过程和上述一样不在赘述

代码为:

代码语言:javascript
复制
i = i + (i >>> 8);i = i + (i >>> 16);

5. 高位清理

因为Integer型32位,最多有32个1,所以最多需要6位存储就足够,其余高位清理掉.

代码如下:

代码语言:javascript
复制
i & 0x3f

这种1的统计方式还有很多种,大多也都是利用位操作处理实现的.

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-07-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 从码农的全世界路过 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档