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,一经查实,立即删除!

相关文章

创建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; 用于查…

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

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

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操作…

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

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

处理机调度与作业调度

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

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

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

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

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

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

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

Python---文件

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

使用docker-compose优雅部署rocketMQ

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

小米的算法部署岗对新手是真的友好

大家好啊&#xff0c;我是董董灿。 自从开始写一些AI行业的岗位介绍&#xff0c;就养成了一个习惯&#xff0c;在上下班的路上经常就会打开某聘瞧一瞧。 导致之前一年不看的某聘认为我要看机会换工作&#xff0c;疯狂给我推猎头&#xff0c;然后电话就进来了。 不堪骚扰的我…

【数据结构】源码角度剖析PriorityQueue

目录 认识 Queue 认识 PriorityQueue PriorityQueue为什么要用二叉堆&#xff1f; PriorityQueue构造方法源码分析 PriorityQueue 的属性 构造方法 JDK1.8传入不可比较的对象 JDK17传入不可比较的对象 传入带有Collection接口的对象 instanceof 关键字 Offer方法分析…

.net core 连接数据库,通过数据库生成Modell

1、安装EF Core Power Tools&#xff1a;打开Vs开发工具→扩展→管理扩展 2、(切记执行这步之前确保自己的代码不存在编写或者编译错误&#xff01;)安装完成后在你需要创建数据库实体的项目文件夹上面单击右键&#xff0c;找到EF Core 工具&#xff08;必须安装扩展之和才会有…

(Ant X6)子组件里的流程图画布无法显示

(Ant X6)子组件里的流程图画布无法显示 问题背景&#xff1a;侧导航页面都是子组件,建模页面的画布无法显示 解决前&#xff1a; 解决后&#xff1a; 解决思路&#xff1a;点击建模菜单时再次加载对应组件 在 Vue 中&#xff0c;每个组件都有一个唯一的 key 属性。当组件的 ke…

第二十章总结。。。

20.1线程简介. 20.2创建线程 2.1继承Thread类 Thread 类是java.lang包中的一个类&#xff0c;从这个类中实例化的对象代表线程&#xff0c;程序员启动一个新线程需要建立Thread 实例。Thread类中常用的两个构造方法如下: public Thread():创建一个新的线程对象。 public Thread…

使用Java将properties转为yaml,保证顺序、实测无BUG版本

使用Java将properties转为yaml 一 前言1.1 顺序错乱的原因1.2 遗漏子节点的原因 二、优化措施三、源码 一 前言 浏览了一圈网上的版本&#xff0c;大多存在以下问题&#xff1a; 转换后顺序错乱遗漏子节点 基于此进行了优化&#xff0c;如果只是想直接转换&#xff0c;可直接…

性能测试:系统架构性能优化

今天谈下业务系统性能问题分析诊断和性能优化方面的内容。这篇文章重点还是谈已经上线的业务系统后续出现性能问题后的问题诊断和优化重点。 系统性能问题分析流程 我们首先来分析下如果一个业务系统上线前没有性能问题&#xff0c;而在上线后出现了比较严重的性能问题&#x…

C++ CryptoPP使用AES加解密

Crypto (CryptoPP) 是一个用于密码学和加密的 C 库。它是一个开源项目&#xff0c;提供了大量的密码学算法和功能&#xff0c;包括对称加密、非对称加密、哈希函数、消息认证码 (MAC)、数字签名等。Crypto 的目标是提供高性能和可靠的密码学工具&#xff0c;以满足软件开发中对…