Java Integer.toBinaryString() 方法源码及原理解析(进制转换、位运算)

news2024/10/6 1:46:39

title: Java Integer.toBinaryString() 方法源码及原理解析(进制转换、位运算)
date: 2022-12-27 17:31:38
tags:

  • Java
    categories:
  • Java
    cover: https://cover.png
    feature: false

1. 使用及源码概览

Integer.toBinaryString() 方法用于将十进制整数转为二进制,如下例:

在这里插入图片描述

完整源码调用如下:

public static String toBinaryString(int i) {
        return toUnsignedString0(i, 1);
}

private static String toUnsignedString0(int val, int shift) {
        // assert shift > 0 && shift <=5 : "Illegal shift value";
        int mag = Integer.SIZE - Integer.numberOfLeadingZeros(val);
        int chars = Math.max(((mag + (shift - 1)) / shift), 1);
        char[] buf = new char[chars];

        formatUnsignedInt(val, shift, buf, 0, chars);

        // Use special constructor which takes over "buf".
        return new String(buf, true);
}

public static int numberOfLeadingZeros(int i) {
        // HD, Figure 5-6
        if (i == 0)
            return 32;
        int n = 1;
        if (i >>> 16 == 0) { n += 16; i <<= 16; }
        if (i >>> 24 == 0) { n +=  8; i <<=  8; }
        if (i >>> 28 == 0) { n +=  4; i <<=  4; }
        if (i >>> 30 == 0) { n +=  2; i <<=  2; }
        n -= i >>> 31;
        return n;
}

static int formatUnsignedInt(int val, int shift, char[] buf, int offset, int len) {
        int charPos = len;
        int radix = 1 << shift;
        int mask = radix - 1;
        do {
            buf[offset + --charPos] = Integer.digits[val & mask];
            val >>>= shift;
        } while (val != 0 && charPos > 0);

        return charPos;
}

final static char[] digits = {
        '0' , '1' , '2' , '3' , '4' , '5' ,
        '6' , '7' , '8' , '9' , 'a' , 'b' ,
        'c' , 'd' , 'e' , 'f' , 'g' , 'h' ,
        'i' , 'j' , 'k' , 'l' , 'm' , 'n' ,
        'o' , 'p' , 'q' , 'r' , 's' , 't' ,
        'u' , 'v' , 'w' , 'x' , 'y' , 'z'
};

2. 解析

2.1 二进制转换

首先,从运算逻辑上了解一下怎么从十进制转换为二进制,一般来说有两种方法,这里只用 8 位来做示范

1、短除法

本质上就是不断除 2,直到商为 0 为止,然后将余数倒序输出。例:15,16

2| 15                      2| 16  
  ————                       ————
  2| 7           1  ^        2| 8             0  ^
    ————            |          ————              |
    2| 3         1  |          2| 4           0  |
      ————          |            ————            |
      2| 1       1  |            2| 2         0  |
        ————        |              ————          |
           0     1  |              2| 1       0  |
                                     ————        |
                                        0     1  |

由上,15 的二进制表示为 0000 1111,16 的二进制表示为 0001 0000

2、按权相加法

即将二进制数首先写成加权系数展开式,依次与二进制位对应,然后按十进制加法规则求和

2 的 0 次方是 1 -------------- 对应第 1 位
2 的 1 次方是 2 -------------- 对应第 2 位
2 的 2 次方是 4 -------------- 对应第 3 位
2 的 3 次方是 8 -------------- 对应第 4 位
2 的 4 次方是 16 -------------- 对应第 5 位
2 的 5 次方是 32 -------------- 对应第 6 位
2 的 6 次方是 64 -------------- 对应第 7 位

例:15 = 2^3 + 2^2 + 2^1 + 2^0,16 = 2^4,即:

15            16
0000 0000     0000 0000
0000 1000     0001 0000
0000 1100
0000 1110
0000 1111

2.2 原码、反码、补码

然后再了解一下原码、反码和补码的相关知识,同样只用 8 位做示范

1、原码

原码,即 2.1 所示的转换为二进制,例:15,原码即为 0000 1111。在 2.1 中只用了正数来举例,这里开始需要区分正数和负数,同时引入符号位的概念。二进制的第一位为符号位,正数为 0,负数为 1,符号位不参与位的转换和运算

例:-15,原码即为 1000 1111

15原码: 0000 1111
-15原码:1000 1111

2、反码

反码,即原码按位取反。这里注意,正数的反码与原码相同。例:-15,原码为 1000 1111,符号位不参与转换,按位取反为 1111 0000

15反码: 0000 1111,与原码同

-15原码:1000 1111

-15反码:1111 0000

3、补码

补码,即反码加 1,完整说法应为原码取反加1。同样注意,正数的补码与原码相同。例:-15,反码为 1111 0000,加 1 为 1111 0001

15补码: 0000 1111,与原码同

-15原码:1000 1111
-15反码:1111 0000
-15补码:1111 0001

正数的原码、反码、补码相同,正数的二进制表示为二进制原码即可(其实也是补码,相同不需要计算)。而负数的二进制表示为二进制补码,也就是文章的第一张图片所示示例,如下。由于 int 为 32 位,所以负数显示了一堆 1,正数则是把前面的 0 去掉了

在这里插入图片描述

2.3 位运算符

再来了解一下位运算的相关知识,同样只用 8 位来示范

1、<<:按位左移运算符

将转换后的二进制左移指定的位数,例:15 << 2,即 0000 1111 << 2,为 0011 1100,十进制表示为 60。这里后面补的都是 0

0000 1111
0011 1100

在这里插入图片描述

注意:这里可以用 1 << n 来表示 2 的 n 次方,因为其实每左移 1 位,就相当于乘以了一个 2

在这里插入图片描述

2、>>:按位右移运算符

将转换后的二进制右移指定的位数,例:15 >> 2,即 0000 1111 >> 2,为 0000 0011,十进制表示为 3。这里前面的补位数是带符号的,正数,符号位为 0,则补 0;负数,符号位为 1,则补 1

0000 1111
0000 0011

在这里插入图片描述

右移可以变相看成是除 2,这时就存在偶数和奇数的情况。偶数,正数和负数的值是相同的,这里说的值指本身的数值,不带符号;奇数时,负数的值比正数大 1

3、>>>:按位右移补零操作符

将转换后的二进制右移指定的位数,移动后的空位以零填充。这里就不区分符号位,因此也叫无符号右移,正数没有影响,因为前面本来就是 0,负数则会改变原本的值大小,例:-15 >>> 2,即 1111 0001 >>> 2,为 0011 1100,这里只用了 8 位来演示,完整 32 位见下图所示,这里前面的 0 被省略了,也可以看到比原来少了两位:

1111 0001
0011 1100

在这里插入图片描述

4、&:如果相对应位都是 1,则结果为 1,否则为 0,例:15 & 16、3 & 7

15: 0000 1111     3: 0000 0011
16: 0001 0000     7: 0000 0111
    0000 0000        0000 0011

则 15 & 16 结果为 0,3 & 7 结果为 3

2.4 源码解析

1、首先来看调用的顶层方法,这里可以看到就是调用了一个 toUnsignedString0() 的方法,参数 i 即我们传进来需要转换的值,这里的 1,表示的是进制位数,1 即二进制,3 则是 8 进制,4 是 16 进制

public static String toBinaryString(int i) {
        return toUnsignedString0(i, 1);
}

public static String toOctalString(int i) {
        return toUnsignedString0(i, 3);
}

public static String toHexString(int i) {
        return toUnsignedString0(i, 4);
}

2、再来看 toUnsignedString0() 方法,这里先调用了一个 Integer.numberOfLeadingZeros() 方法,这个方法主要用来计算二进制表示的高位连续 0 位的数量,然后用 Integer.SIZE(32) 减去这个数量,计算需要表示的字符数组的长度,可以理解为省略了前面的 0。如下例:

15:0000 0000 0000 0000 0000 0000 0000 1111,原本的表示
15:1111,实际的表示

这一步就可以理解为把前面的 0 省略掉了,只保留需要表示的位数

private static String toUnsignedString0(int val, int shift) {
        // assert shift > 0 && shift <=5 : "Illegal shift value";
        int mag = Integer.SIZE - Integer.numberOfLeadingZeros(val);
        int chars = Math.max(((mag + (shift - 1)) / shift), 1);
        char[] buf = new char[chars];

        formatUnsignedInt(val, shift, buf, 0, chars);

        // Use special constructor which takes over "buf".
        return new String(buf, true);
}

@Native public static final int SIZE = 32;

3、接下来来看 Integer.numberOfLeadingZeros() 的具体实现,这里用了一个简易的二分法,分为多个区间 [16, 24, 28, 30] 来进行判断。这里还剩下 [30, 32] 这个区间,为什么没有算呢?见下面的第 6 步。这里的 n 表示高位连续 0 的数量

  1. 首先判断 i 是否为 0,0 的话则是 32 位的高位连续 0,直接返回 32
  2. 然后判断 i >>> 16 是否为 0,可以理解为先二分判断一半的区间,假如为 0,则表示至少包含 16 个高位连续 0,n 加上 16,然后 i <<= 16,将 i 去除 16 位 0 再进行后续判断
  3. 判断 i >>> 24 是否为 0,即判断是否至少包含 8 个高位连续 0,假如为 0,则 n 加上 8,然后 i <<= 8,将 i 去除 8 位 0 再进行后续判断
  4. 同上,判断是否至少包含 4 个高位连续 0
  5. 同上,判断是否至少包含 2 个高位连续 0,这里已经为 i >>> 30
  6. 最后,这里还剩下了 [30, 32] 这个长度为 2 的区间,存在四种情况,[00, 01, 10, 11],在最前面我们已经判断了等于 0 的情况,所以 00 是排除掉的,剩下 [01, 10, 11],假如是 x1,那么就不需要判断 x 是多少了,因为只需要判断最高连续 0 位;假如是 x0,由于 00 已经排除,则 x 为 1。所以,其实只需要判断一位就足够了,这也是为什么没有算 [30, 32] 这个区间,只到了 31 为止
    这里先给 n 赋了初始值为 1,先假设默认这一位是 0,然后再通过 n -= i >>> 31,判断这一位到底是什么,是 0 则 n = n - 0,不变;是 1 则 n = n - 1,减掉原先赋的默认初始值 1。这里其实就是用这个技巧代替了 if (i >>> 31 == 0) { n += 1; } 的判断,如下第二种写法
public static int numberOfLeadingZeros(int i) {
        // HD, Figure 5-6
        if (i == 0)
            return 32;
        int n = 1;
        if (i >>> 16 == 0) { n += 16; i <<= 16; }
        if (i >>> 24 == 0) { n +=  8; i <<=  8; }
        if (i >>> 28 == 0) { n +=  4; i <<=  4; }
        if (i >>> 30 == 0) { n +=  2; i <<=  2; }
        n -= i >>> 31;
        return n;
}

public static int numberOfLeadingZeros(int i) {
        // HD, Figure 5-6
        if (i == 0)
            return 32;
        int n = 0;
        if (i >>> 16 == 0) { n += 16; i <<= 16; }
        if (i >>> 24 == 0) { n +=  8; i <<=  8; }
        if (i >>> 28 == 0) { n +=  4; i <<=  4; }
        if (i >>> 30 == 0) { n +=  2; i <<=  2; }
        if (i >>> 31 == 0) { n += 1; }
        return n;
}

4、再回到 toUnsignedString0() 这个方法,通过调用 numberOfLeadingZeros() 得到高位连续 0 的数量,然后通过 Integer.SIZE 减去这个数量得到需要表示的位数

然后再通过 Math.max(((mag + (shift - 1)) / shift), 1); 来计算 2/8/16 进制对应的字符数组的长度,这里的参数 shift 前面提到是用来表示进制位数,1 即二进制,3 则是 8 进制,4 是 16 进制

得到字符数组长度后创建对应的字符数组,调用 formatUnsignedInt() 来填充数组

private static String toUnsignedString0(int val, int shift) {
        // assert shift > 0 && shift <=5 : "Illegal shift value";
        int mag = Integer.SIZE - Integer.numberOfLeadingZeros(val);
        int chars = Math.max(((mag + (shift - 1)) / shift), 1);
        char[] buf = new char[chars];

        formatUnsignedInt(val, shift, buf, 0, chars);

        // Use special constructor which takes over "buf".
        return new String(buf, true);
}

5、formatUnsignedInt() 方法如下,参数 val 是需要转换的值;shift 表示进制位数,这里为 1;buf 为创建的字符数组;offset 为偏移量,这里为 0;len 为数组长度。其中第 6 行用到的 Integer.digits 是定义好的包含全部数字和字母的字符数组

这里其实就是按照对应的位数进行对应填充

  1. 将数组长度赋值给 charPos,后续用 charPos 来进行计算

  2. radix = 1 << shift,前面说过,1 << n 其实就是 2 的 n 次方,这里是用来表示进制,二进制则是 2 的 1 次方,八进制是 2 的 3 次方,十六进制是 2 的 4 次方

  3. mask 为进制减一,用来后续和 val 做 & 运算,实际就是逐批匹配进制对应的位数。例:shift 为 3,即 8 进制,mask 则为 7

    8 == radix = 1 << 3;
    7 == mask = radix - 1;

    mask 做 & 运算时,表示为 111,即 3 位二进制表示一位 8 进制

  4. val & mask 得到值后,在 digits 数组里去找到对应的索引的字符赋给 buf,即创建的字符数组,注意,这里是倒序存放,对应进制位数的变化,从右往左

  5. 然后将 val 右移相应的进制位数,循环匹配

  6. 最后返回填充好的字符数组

static int formatUnsignedInt(int val, int shift, char[] buf, int offset, int len) {
        int charPos = len;
        int radix = 1 << shift;
        int mask = radix - 1;
        do {
            buf[offset + --charPos] = Integer.digits[val & mask];
            val >>>= shift;
        } while (val != 0 && charPos > 0);

        return charPos;
}

final static char[] digits = {
        '0' , '1' , '2' , '3' , '4' , '5' ,
        '6' , '7' , '8' , '9' , 'a' , 'b' ,
        'c' , 'd' , 'e' , 'f' , 'g' , 'h' ,
        'i' , 'j' , 'k' , 'l' , 'm' , 'n' ,
        'o' , 'p' , 'q' , 'r' , 's' , 't' ,
        'u' , 'v' , 'w' , 'x' , 'y' , 'z'
};

6、再回到 toUnsignedString0() 方法,最后一步则是将字符数组转换为 String 返回,到这里整个流程结束

private static String toUnsignedString0(int val, int shift) {
        // assert shift > 0 && shift <=5 : "Illegal shift value";
        int mag = Integer.SIZE - Integer.numberOfLeadingZeros(val);
        int chars = Math.max(((mag + (shift - 1)) / shift), 1);
        char[] buf = new char[chars];

        formatUnsignedInt(val, shift, buf, 0, chars);

        // Use special constructor which takes over "buf".
        return new String(buf, true);
}

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

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

相关文章

【1 - 决策树 - 案例部分:泰坦尼克号幸存者预测】菜菜sklearn机器学习

课程地址&#xff1a;《菜菜的机器学习sklearn课堂》_哔哩哔哩_bilibili 第一期&#xff1a;sklearn入门 & 决策树在sklearn中的实现第二期&#xff1a;随机森林在sklearn中的实现第三期&#xff1a;sklearn中的数据预处理和特征工程第四期&#xff1a;sklearn中的降维算法…

Spring RCE漏洞CVE-2022-22965复现与JavaFx GUI图形化漏洞利用工具开发

文章目录前言一、漏洞描述二、影响范围三、漏洞复现3.1 漏洞poc四、修复建议五、图形化工具开发-Rexbb简介与使用5.1 工具使用总结前言 换了新工作&#xff0c;年底了比较多自学的时间&#xff0c;所以把今年比较经典的漏洞给复现一下&#xff0c;之前因为没空&#xff0c;只复…

python--PyCHarm里代码整体向左/右缩进

1、PyCharm 里代码整体向左缩进 鼠标选中多行代码&#xff0c;同时按住 Shift Tab 键&#xff0c;实现一次向左缩进4个字符。 2、PyCharm 里代码整体向右缩进 鼠标选中多行代码&#xff0c;按下 Tab 键&#xff0c;实现一次向右缩进4个字符。 3、PyCharm 里光标变粗 光标…

信创大提速,企业如何在高速行驶中更换“发动机”?

导读&#xff1a;信创替代如何平滑迁移不“翻车”&#xff1f; 经过多年发展&#xff0c;中国信创产业迎来全面提速。 2022年国家下发的79号文中&#xff0c;全面给出了国资信创产业发展与进度的指导&#xff0c;要求到2027年央企国企100%完成信创替代&#xff0c;替换范围涵盖…

SprintBoot实战(十一)集成 xxl-job

目录1.简介2.Maven依赖3.初始化数据库4.调度中心配置5.执行器配置6.项目结构7.访问页面8.新建定时任务9.BEAN运行模式10.GLUE(Java)运行模式1.简介 xxl-job&#xff1a; 是一个分布式任务调度平台。平台架构分为 “调度中心” 和 “执行器”。调度中心类似定时任务的注册中心&a…

SuperMap iDesktop 之 BIM优化流程——建筑篇

kele 一、背景介绍 BIM数据是三维系统中的常客&#xff0c;它具有信息完备性、信息关联性、信息一致性、 可视化、协调性、模拟性、优化性和可出图性八大特点&#xff0c;广受人们喜爱&#xff0c;但这也使得它自身数据量庞大&#xff0c;在项目中展示效果不尽人意&#xff0c…

SegeX MemDialog:封装好的内存对话框(非资源对话框)说明

----哆啦刘小洋 原创&#xff0c;转载需说明出处 2022-12-27 SegeX MemDialog1 简介2 源文件清单3 快速测试4 实现基本原理5 主要功能6 使用方法6 .1 简单界面6.2 复杂界面1 简介 SegeX组件之一&#xff1a;SegeX MemDialog&#xff0c;应用级内存对话框封装类。源码首次公开。…

IEC 60598-2-22-2021 灯具 第2-22部分- 特殊要求-应急照明灯具.

2021年12月6日&#xff0c;国际电工委员会发布标准IEC 60598-2-22:2021《灯具-第2-22部分&#xff1a;特殊要求-应急照明灯具》。IEC 60598-2-22:2021标准以IEC 60598-2-22:2021 RLV标准的形式提供&#xff0c;包含国际标准及其红线版本&#xff0c;内容显示了与前一版本相比对…

tensorflow06——正则化缓解过拟合

正则化主要是在损失函数中引入了第二个部分&#xff0c;模型复杂度&#xff0c;具体就是对w参数赋予了权值&#xff0c;并求和&#xff0c;再乘上一个超参数。 &#xff08;利用给w加上权值&#xff0c;弱化训练数据的噪声&#xff09; 大概可以理解为这个意思假设模型有两个参…

从0-1搭建流媒体系统之ZLMediaKit 安装、运行、推流、拉流

音视频开发系列 文章目录音视频开发系列前言一、ZLMediaKit是什么&#xff1f;二、使用过程1.编译、安装、运行2.推流、拉流总结前言 目前、比较有名的流媒体服务器有ZLMediaKit、srs、live555、eadydarwin等。因为srs是单线程服务、对于多核服务器的支持需要通过部署多个服务…

蓝牙学习七(MAC地址)

1.简介 一个BLE设备&#xff0c;可以使用两种类型的地址&#xff08;一个BLE设备可以同时具备两种地址&#xff09;&#xff1a;Public Device Address&#xff08;公共设备地址&#xff09;和Random Device Address&#xff08;随机设备地址&#xff09;。而Random Device Add…

如何用 java 实现【二叉搜索树】

文章目录搜索树概念1. 查找操作2. 插入操作3. 删除操作4. 以上三种操作的测试5. 性能分析搜索树概念 二叉搜索树 又称 二叉排序树&#xff0c;它或者是一棵空树&#xff0c;或者是具有以下性质的二叉树&#xff1a; 若它的 左 子树 不为空&#xff0c;则 左 子树上所有节点的值…

自定义神经网络入门-----Pytorch

文章目录目标检测的相关评价指标IoUmAP正例和负例准确率P召回率R准确率ACCP-R曲线--APnn.Module类全连接层感知机类使用nn.Sequential进行构造使用randn函数进行简单测试损失函数nn.functionalnn.optim模型处理网络模型库torchvision.models模型Fine-tune和save参考目标检测的相…

【STM32F4系列】【HAL库】【自制库】模拟IIC从机

介绍 本项目是利用GPIO模拟I2C的从机 网上常见的是模拟I2C主机 本项目是作为一个两个单片机之间低速通信的用法 协议介绍请看,传送门 模拟主机请看这里 从机 功能 实现I2C从机端读写寄存器 编程思路 I2C的从机实现比起主机来麻烦一些 因为SCL的时序是由主机发送,从机需…

【nowcoder】笔试强训Day12

目录 一、选择题 二、编程题 2.1二进制插入 2.2 查找组成一个偶数最接近的两个素数 一、选择题 1.以下方法&#xff0c;哪个不是对add方法的重载? public class Test {public void add( int x,int y,int z){} } A. public int add(int x,int y,float z){return 0;} B.…

Go语言设计与实现 -- WaitGroup, Once, Cond

WaitGroup 我们可以通过 sync.WaitGroup 将原本顺序执行的代码在多个 Goroutine 中并发执行&#xff0c;加快程序处理的速度。 我们来看一下sync.WaitGroup的结构体&#xff1a; type WaitGroup struct {//保证WaitGroup不会被开发者通过再赋值的方式复制noCopy noCopy// 64-…

重学redux之Redux-Thunk高级使用(三)

这是第三篇了,哥们,如果没看过前两篇,可以去看看之前的两篇,有基础的可以直接看,不多说,直接开讲 默认情况下,Redux 的动作是同步调度的,对于任何需要与外部 API 通信或执行副作用的应用程序来说都是一个问题。 Redux 允许中间件位于被分派的动作和到达 reducer 的动…

抖音本地生活的蓬勃发展,离不开服务商的推波助澜

抖音本地生活&#xff0c;已经势不可挡01 抖音公布本地生活成绩单&#xff0c;交易额增长30倍抖音经过6年时间的演变&#xff0c;产品功能日益丰富&#xff0c;已经从内容消费&#xff0c;延续到线上购物、线下团购等领域&#xff0c;从最初的记录美好生活&#xff0c;成为一种…

统计分析工具-FineReport配置SQL Server外接数据库(2)

1. 配置外接数据库 1.1 外接数据库配置入口 外接数据库的配置入口&#xff0c;有三种形式&#xff1a; 1&#xff09;超级管理员第一次登录数据决策系统时&#xff0c;即可为系统配置外接数据库。如下图所示&#xff1a; 2&#xff09;对于使用内置数据库的系统&#xff0c;管…

站点能源低碳目标网,助力网络碳中和 | 华为发布站点能源十大趋势

2022年12月29日&#xff0c;华为今天举办站点能源十大趋势发布会并重磅发布白皮书。发布会上&#xff0c;华为站点能源领域总裁尧权全面解读了能源数字化、低碳网络、站点供电绿色化等站点能源十大趋势。 尧权表示&#xff0c;2022年是不平凡的一年&#xff0c;全球能源危机背…