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.mzph.cn/news/185836.shtml

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

相关文章

000 - 前言

目录 问题1&#xff1a;学Qt之前是不是得先把C基础打好才行&#xff1f;会一点点C可以学吗&#xff1f; 问题2&#xff1a;我已经会 PyQt 了&#xff0c;还有必要再学 Qt 吗&#xff1f; 问题3&#xff1a;Qt 和 QML 学哪个比较好呢&#xff1f; 大家好呀~ 我是角角。从今天…

创建JDK8版本的SpringBoot项目的方法

目录 一.通过阿里云下载 二.通过IDEA创建 1.下载安装JDK17 2.创建SpringBoot 3.X的项目 3.把JDK17改成JDK8 截止到2023.11.24&#xff0c;SpringBoot不再支持3.0X之前的版本&#xff0c;3.0X之后的版本所对应的JDK版本为JDK17&#xff0c;下面介绍如何在idea上继续使用JDK…

解析javascript数组方法 find 和 filter 有何区别

首先用一个案例可以很直观的看到 find 和 filter 的区别&#xff1b; 相同点&#xff1a; 两者分别可以接受三个参数&#xff1a;当前元素、当前索引、整个数组&#xff1b;两者都可以用来查找数组中符合条件的元素&#xff1b; 不同点&#xff1a; find&#xff1a; 用于查…

YOLOv8+Nanodet强强联合改进标签分配:使用NanoDet动态标签分配策略,同时集成VFL全新损失,来打造新颖YOLOv8检测器

💡本篇内容:YOLOv8+Nanodet强强联合改进标签分配:使用NanoDet动态标签分配策略,同时集成VFL全新损失,来打造新颖YOLOv8检测器 💡🚀🚀🚀本博客 YOLO系列 + 改进NanoDet模型的动态标签分配策略源代码改进 💡一篇博客集成多种创新点改进:VFL损失函数 + Nanodet…

C/C++不定参数的使用

文章目录 C语言的不定参C的不定参 C语言的不定参 C语言的不定参数最常见的应用示例就是printf函数&#xff0c;如下&#xff0c;参数列表中的...表示不定参数列表 #include <stdio.h> int printf(const char *format, ...);试着模拟实现C语言的printf函数 void myprin…

Android:BackStackRecord

BackStackRecord:fragment回退栈,继承自FragmentTransaction,并且实现了OpGenerator接口,OpGenerator接口用来添加或弹出事务的,后面会提到。 从《Android:从源码看FragmentManager如何工作》文章知道,每次beginTransaction会创建一个BackStackRecord对象,改对象持有f…

C++基础——文件操作

文章目录 1 概述2 文本文件2.1 写文件2.1.1 写文件流程2.1.2 文件打开方式 2.2 读文件 3 二进制文件3.1 写文件3.2 读文件 1 概述 程序最基本的操作之一就是文件操作&#xff0c;程序运行时的数据都是临时数据&#xff0c;当程序结束后就不复存在了。通常都是通过文件或其他持…

【vue实战项目】通用管理系统:信息列表,信息录入

本文为博主的vue实战小项目系列中的第六篇&#xff0c;很适合后端或者才入门的小伙伴看&#xff0c;一个前端项目从0到1的保姆级教学。前面的内容&#xff1a; 【vue实战项目】通用管理系统&#xff1a;登录页-CSDN博客 【vue实战项目】通用管理系统&#xff1a;封装token操作…

深入理解HTTP协议

一、简介 超文本传输协议&#xff08;英文&#xff1a;HyperText Transfer Protocol&#xff0c;缩写&#xff1a;HTTP&#xff09;是一种用于分布式、协作式和超媒体信息系统的应用层协议。HTTP是万维网的数据通信的基础。 二、协议概述 HTTP是一个客户端终端&#xff08;用…

栈模拟先序后序中序遍历(非递归遍历)

先序遍历&#xff1a; vector<int> preorderTraversal(TreeNode* u) {stack<TreeNode*>stk;vector<int>res;if(unullptr) return res;while(stk.size()||u){if(u){res.push_back(u->val);//遍历当前结点stk.push(u);//记录当前递归层uu->left;//遍历左…

显示Excel功能区或工具栏的方法不少,其中快捷方式最快

Microsoft Excel是Office套件中最复杂的工具之一&#xff0c;它提供了大量功能&#xff0c;其中大部分都是使用工具栏操作的。缺少工具栏使Excel很难完成工作。 如果Excel中没有这些关键元素&#xff0c;你将无法快速完成工作&#xff0c;因此&#xff0c;可以理解的是&#x…

处理机调度与作业调度

处理机调度 一个批处理型作业&#xff0c;从进入系统并驻留在外存的后备队列上开始&#xff0c;直至作业运行完毕&#xff0c;可能要经历如下的三级调度 高级调度 也称为作业调度、长程调度、接纳调度。调度对象是作业 主要功能&#xff1a; 挑选若干作业进入内存 为作业创建…

flutter Running Gradle task ‘assembleDebug‘

flutter Running Gradle task assembleDebug Running Gradle task assembleDebug新问题描述新问题解决方案Running Gradle task ‘assembleDebug’ 用Android Stduio创建Flutter项目的时候,会出现各种问题,踩了一个又一个,最后编译的时候可能会出现一直显示Running Gradle …

在Pycharm中创建项目新环境,安装Pytorch

在python项目中&#xff0c;很多项目使用的各类包的版本是不一致的。所以我们可以对每个项目有专属于它的环境。所以这个文章就是教你如何创建新环境。 一、创建新环境 首先我们需要去官网下载conda。然后在Pycharm下面添加conda的可执行文件。 用conda创建新环境。 二、…

多要素气象环境监测站知识科普

随着工业化和城市化的快速发展&#xff0c;气象环境的影响越来越受到人们的关注。为了更好地保护我们的环境&#xff0c;一款WX-CQ12 多要素气象环境监测站应运而生。这款监测站可以全方位地监测气象环境中的温度、湿度、气压、风速、风向、雨量、太阳辐射等重要要素&#xff0…

Python实现的顺序查找算法

一、算法过程描述&#xff1a; 最基本的查找技术的过程&#xff1a;从表中的第一个记录开始&#xff0c;逐个进行记录的 关键字和给定值比较&#xff0c;若某个记录的关键字和给定值相等&#xff0c;则查找成功&#xff0c;找到所查的记录&#xff1b; 如果直到最后一个记录&am…

vue3高德地图使用,地址搜索,地址逆解析

在vue3项目里使用高德地图 高德地图文档 先在项目的index.html页面里添加一些东西 <script type"text/javascript">window._AMapSecurityConfig {securityJsCode: "xxxxxxxxxxxxx", //高德安全码};</script> <script src"https://…

Python---文件

文件--- 内存中存放的数据在计算机关机后就会消失。要长久保存数据&#xff0c;就要使用硬盘、光盘、U 盘等设备。为了便于数据的管理和检索&#xff0c;引入了“文件”的概念。 一篇文章、一段视频、一个可执行程序&#xff0c;都可以被保存为一个文件&#xff0c;并赋予一个…

@FeignClient本地项目启动调用测试服务地址,并兼容发布测试环境

1.FeignClient对应api接口的使用如下&#xff1a; /*** author ys* description 对外服务接口*/ FeignClient(name "xx-shop", path "/shop", url "${api-third.xx-shop:}") public interface ExterlControllerApi {/*** 获取所有的类型** p…

使用docker-compose优雅部署rocketMQ

使用docker-compose优雅部署RocketMQ 随着市场的发展&#xff0c;越来越多的复杂场景出现在我们日常的开发工作中。随之也越来越多的好的工具&#xff0c;也同步出现在程序员的学习范围清单内。好的工具提高产品性能的同时&#xff0c;也带来了很多安装上的问题&#xff0c;do…