Java中的Integer.bitCount浅析

文章目录

  • Java中的Integer.bitCount浅析
    • 问题
    • 思考
    • Integer.bitCount
      • 解释
      • 拓展

Java中的Integer.bitCount浅析

原文链接

问题

有一个整数x,我们需要统计该整数的二进制表示中包含的1的个数。这个也被称为汉明重量(Hamming weight)

例如,整数13的二进制表示是1101,其中有3个1,因此统计出的结果是3。

思考

看到这个问题的时候可能的想法就是遍历一遍这个二进制数位并统计结果。

对于统计32位的整数,下面是其中的一种做法:

 int bitCount(int x) {int count = 0;for (int i = 0; i < 32; i++) {int t = x & 1;//count+=t;if (t == 1) {count++;}x >>= 1;}return count;}

这种做法时间复杂度是O(n),n是二进制数的位数

Integer.bitCount

在Java中也有提供统计整数数位1数量的方法Integer.bitCount,下面是它的源码:

public static int bitCount(int i) {// HD, Figure 5-2i = 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;}

这个方法的代码第一眼看过去有点看不明白,但是我们可以很直观得看到这个方法里面并没有用到循环统计的方法,而是进行了几步位运算和算数运算就可以统计出来了,它是怎么做到的呢?

解释

直接举个例子:
对于数值为1822569234的32位整数,它的32位二进制表示为01101100101000100011001100010010,其中有131,计算过程如下:

对上图做出解释:

  1. 首先我们对二进制进行分组,每组一个比特位,这样一开始有32组,假设为每个分组编号,从左到右即分组1,分组2,......,分组32
  2. 接着将每两个相邻的组进行求和,即分组1和分组2分组3和分组4,…,分组31和分组32,这样我们就可以先求出每两位中1的数量

0+0=00(等于十进制0)
0+1=011+0=01(等于十进制1)
1+1=10(等于十进制2)

  1. 结果放到求和组的数位上,形成新的分组,每组中有两个比特位,这些组的值就是每两个比特位中1的数量,可以重新编号分组1,分组2......分组15,分组16
  2. 继续对相邻的组进行求和,组成每4个比特位为一组的分组,依此类推,直到每32位为一组就是答案了。

这个过程其实利用了分治的思想,将一个大的问题,分解为多个小的子问题,先对子问题进行求解,最后将子问题合并得出结果。

这个过程用代码写出来如下:

  static int bitCount(int x) {x = (x & 0b01010101010101010101010101010101) + ((x >> 1) & 0b01010101010101010101010101010101);x = (x & 0b00110011001100110011001100110011) + ((x >> 2) & 0b00110011001100110011001100110011);x = (x & 0b00001111000011110000111100001111) + ((x >> 4) & 0b00001111000011110000111100001111);x = (x & 0b00000000111111110000000011111111) + ((x >> 8) & 0b00000000111111110000000011111111);x = (x & 0b00000000000000001111111111111111) + ((x >> 16) & 0b00000000000000001111111111111111);return x;}

在大部分编程语言中要表示二进制数,可以在其前面加上0b0B前缀。

对于上述代码中,第一行就是求每个分组是1个比特位时,相邻分组的和。(x & 0b01010101010101010101010101010101)就是将分组1的值置零,保留分组2的值;分组3的值置零,保留分组4的值…依此类推。((x >> 1) & 0b01010101010101010101010101010101)
就是先将相邻分组中的第一个分组移到自己相邻的分组,即分组1的值移动到分组2分组2分组3,分组3分组4…,之后再将分组1,分组3,分组5…等置零,避免影响求和的结果。最后将(x & 0b01010101010101010101010101010101)((x >> 1) & 0b01010101010101010101010101010101)求和,就是第一次相邻分组的求和结果了,接着后面只是每个分组的比特位变多了,过程还是一样,最终得到32个比特位为一组时就是结果了。

这样的算法时间复杂度就是 O ( log ⁡ 2 n ) O(\log_2{n}) O(log2n),n是二进制数的位数

我们可以将上面的代码中的数值用十六进制表示:

static int bitCount(int x) {x = (x & 0x55555555) + ((x >> 1) & 0x55555555);x = (x & 0x33333333) + ((x >> 2) & 0x33333333);x = (x & 0x0F0F0F0F) + ((x >> 4) & 0x0F0F0F0F);x = (x & 0x00FF00FF) + ((x >> 8) & 0x00FF00FF);x = (x & 0x0000FFFF) + ((x >> 16) & 0x0000FFFF);return x;}

和Java中的进行比较

public static int bitCount(int i) {// HD, Figure 5-2i = 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;}

很明显这个和Java中的bitCount还是不一样呀?其实Java中的bitCount是在这个代码基础上减少了一些不必要的运算的结果。

  1. 第一行 x = (x & 0x55555555) + ((x >> 1) & 0x55555555)其实可以等效于x = x - ((x >> 1) & 0x55555555),可以减少一次与运算
  2. 第二行,这个是求每个分组有2个比特位的时候的和,因为分组n分组n+1相加的结果最大是4,二进制表示即100,超过分组n+1的2比特位,所以只能保留原样
  3. 第三行,这个是求每个分组有4个比特位的时候的和,因为分组n分组n+1相加的结果最大时8,二进制表示即1000,没有超过分组n+1的4比特位,所以可以先对原来的数字和移位的数字进行求和,之后要对分组n的位置置零,避免对后面求和结果造成影响,也就是需要与上0x0F0F0F0F
  4. 第四行,这个是求每个分组有8个比特位的时候的和,因为分组n分组n+1相加的结果最大时16,二进制表示即10000,没有超过分组n+1的8比特位,所以可以和第三行那样先求和。而且32比特位中1的数量最大也就是32个,二进制表示即100000,只有6个比特位,所以结果也不会超过8个比特位,不需要对分组n置零,就是不需要与运算。
  5. 第五行,这个是求每个分组有16个比特位的时候的和,同第四行一样可以直接求。
  6. 最后的结果保存在低位的6个比特位置上,所以需要与上0b111111,换成十六进制就是0x3F

拓展

理解了32个数位的做法,那推广到64个数位也可以利用上述的思想。

下面是Java中Long.bitCount的源码

public static int bitCount(long i) {// HD, Figure 5-2i = i - ((i >>> 1) & 0x5555555555555555L);i = (i & 0x3333333333333333L) + ((i >>> 2) & 0x3333333333333333L);i = (i + (i >>> 4)) & 0x0f0f0f0f0f0f0f0fL;i = i + (i >>> 8);i = i + (i >>> 16);i = i + (i >>> 32);return (int)i & 0x7f;}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/234671.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

最简单的链路追踪收集器

链路追踪可帮助您快速了解程序服务之间的调用关系&#xff0c;并快速洞悉内部发生的情况。主流的链路追踪系统有zipkin,jaeger,skywalking等&#xff0c;由于opentelemetry的存在&#xff0c;都具有opentelemetry的转换器。 我们利用opentelemetry来进行zipkin,jaeger,skywalk…

Python list列表添加元素的3种方法及删除元素的3种方法

Python list列表添加元素的3种方法 Python list 列表增加元素可调用列表的 append() 方法&#xff0c;该方法会把传入的参数追加到列表的最后面。 append() 方法既可接收单个值&#xff0c;也可接收元组、列表等&#xff0c;但该方法只是把元组、列表当成单个元素&#xff0c;这…

Jmeter接口测试:jmeter_HTTP Cookie管理器看这一篇文章就够了

HTTP Cookie管理器 HTTP Cookie管理器可以像浏览器一样自动存储和发送cookie&#xff0c;以这种自 动收集的方式收集到的cookie不会在cookie manager中进行展示&#xff0c;但是运行后&#xff0c; 可以通过 查看结果树&#xff08;监听器&#xff09;可以查看到cookie信息 除…

西南科技大学数字电子技术实验一(数字信号基本参数与逻辑门电路功能测试及FPGA 实现)FPGA部分

一、 实验目的 1、掌握基于 Verilog 语言的 diamond 工具设计全流程。 2、熟悉、应用 Verilog HDL 描述数字电路。 3、掌握 Verilog HDL 的组合和时序逻辑电路的设计方法。 4、掌握“小脚丫”开发板的使用方法。 二、 实验原理 与门逻辑表达式:Y=AB 原理仿真图: 2 输入…

docker-compose;私有镜像仓库harbor搭建;镜像推送到私有仓库harbor

docker-compose&#xff1b;私有镜像仓库harbor搭建&#xff1b;镜像推送到私有仓库harbor 文章目录 docker-compose&#xff1b;私有镜像仓库harbor搭建&#xff1b;镜像推送到私有仓库harbordocker-compose私有镜像仓库harbor搭建镜像推送到私有仓库harbor docker-compose D…

LLM 分布式训练框架 | DeepSpeed与Accelerate

&#x1f680; 简单记录下根据网上资料&#xff08;如Reference中所列&#xff09;所学到的一些知识&#xff0c;这里主要介绍的是deepspeed分布式训练框架相关概念。 &#x1f604;小日记&#xff1a;今天太舒服了&#xff0c;早上跑了6km&#xff0c;晚上吃了养生菌菇火锅~ …

Ubuntu+Tesla V100环境配置

系统基本信息 nvidia-smi’ nvidia-smi 470.182.03 driver version:470.182.03 cuda version: 11.4 查看系统体系结构 uname -aUTC 2023 x86_64 x86_64 x86_64 GNU/Linux 下载miniconda https://mirrors.tuna.tsinghua.edu.cn/anaconda/miniconda/?CM&OA https://mi…

Python入门06布尔值

目录 1 什么是布尔值2 怎么生成布尔值3 在控制程序中使用布尔值4 数据过滤、排序和其他高级操作总结 1 什么是布尔值 首先我们要学习一下布尔值的定义&#xff0c;布尔值是一种数据类型&#xff0c;它只有两个可能的值&#xff1a;True&#xff08;真&#xff09;或 False&…

Linux系统部署Tale个人博客并发布到公网访问

文章目录 前言1. Tale网站搭建1.1 检查本地环境1.2 部署Tale个人博客系统1.3 启动Tale服务1.4 访问博客地址 2. Linux安装Cpolar内网穿透3. 创建Tale博客公网地址4. 使用公网地址访问Tale 前言 今天给大家带来一款基于 Java 语言的轻量级博客开源项目——Tale&#xff0c;Tale…

1000多页!LeetCode刷题手册分享

这本手册确实是一部令人印象深刻的作品。&#xff08;手册链接在文末&#xff01;&#xff01;&#xff01;&#xff09; 首先&#xff0c;内容充实是这本手册的一大亮点。它涵盖了广泛的算法和数据结构主题&#xff0c;包括数组、链表、树、图、排序算法、动态规划等等。每个…

CodeTON Round 7 (Div. 1 + Div. 2, Rated, Prizes!)

B. AB Flipping 老规矩&#xff0c;自己要模拟一遍样例&#xff0c;发现样例还不够&#xff0c;就自己构造样例&#xff0c;这样做着做着就会有思路。 分析&#xff1a;假如现在有这样一个字符串 BBBAABABBAAA。会发现前三个和后三个一定是不会被操作的&#xff0c;因为不会满…

Multi-head attention机制

多头&#xff1a;多个相同结构的线性变换层&#xff08;方阵&#xff09;&#xff0c;要求分别线性变换 B站教学视频参考&#xff1a;https://www.bilibili.com/video/BV1eG4y1N7Jp/?p17&spm_id_frompageDriver&vd_sourcef4c7dcac0ad5ae8189bd414a3b23020d 什么是多头…