Integer类的toBinaryString源码分析
jdk1.8( jdk11代码有所不同) 的Integer包装类中提供了已经封装好的进制转换函数 toBinaryString(), toOctalString(), toHexString(),下面分析一下它们的源码。
//转二进制
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);
}
从以上源码可以看出,它们都调用了toUnsignedString0()这个函数,只是第二个参数shift的值不同.
- toUnsignedString()
接下来我们看看 toUnsignedString()是怎么实现的.
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方法填充字符数组,得到所需进制表示的字符串并返回
formatUnsignedInt(val, shift, buf, 0, chars);
return new String(buf, true);
}
第一步中最关键的字符数组长度的计算是通过numberOfLeadingZeros()方法计算int变量的计算机二进制表示的高位连续0位的数量,进而获得最高非0位到最低位的长度,也就是需要表示的位数, 负数的最高位是1。例如整数18在计算机中的二进制存储为0000,0000,0000,0000,0000,0000,0001,0010,那么需要表示的部分便是1,0010,前面的28位均为0,不用表示。而-18在计算机中的二进制存储为 11111111111111111111111111101110, 都须要表示.
- numberOfLeadingZeros()
这个函数一般给出的解决方案是通过判断高位是否为0,然后移位重复判断,记录连续的0的个数。如下列代码:
public static int numberOfLeadingZeros0(int i){
if(i == 0) {
return 32; // i为0,则32位全为0
}
int n = 0;
int mask = 0x80000000; // 即 1000 0000 0000 0000 0000 0000 0000 0000
int j = i & mask; //取出符号位,如符号位为 1,表示负数,则直接返回n=0.
//从高位向低位搜索连续出现的0的个数
while(j == 0){
n++; // 计数0出现的次数
i <<= 1; // <<表示左移移位,不分正负数,低位补0
j = i & mask; // 取出左移后最高位的值。
}
return n;
}
以上方法的平均时间复杂度是o(k),k为变量i的位数,如果i为int,k就是32。这种很基本的库函数会经过很多次的调用,需要进一步的优化.
java源码中的写法更加优秀:
//求从高位向低,连续出现的0的个数
public static int numberOfLeadingZeros(int i) {
if (i == 0)
return 32;
int n = 1;
// >>>表示无符号右移,也叫逻辑右移,即若该数为正,则高位补0,而若该数为负数,则右移后高位同样补0
if (i >>> 16 == 0) { n += 16; i <<= 16; } // <<表示左移移,不分正负数,低位补0
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;
}
这个地方采用的是分治法. (可以想想二分搜索的过程)
意思就是通过划分16,8,4,2等逐步缩小的四个区间进行判断和移位。不管if的代码块是否执行,需要判断的位的区间也在随着代码的推进而逐步缩减。它通过分层次的缩小需要求解的域的范围,减少计算次数.
分析过程如下:
if( i>>>30==0 )判断完后还有两位,为什么最后 n-= i>>>31, 这里i 右移了31位,等于只留下了最高位,那么第二高位怎么没有处理呢?
因为,在方法的开始已经判断并返回了变量为0即所有的位均为0的情况,所以能进行到后面的代码部分,说明变量不为0,即必然存在至少一个位为1,而我们在选择需要讨论的位区间时,始终选择的是包含位为1的区间进行讨论。所以,在最后区间长度变为2时,区间中必然至少存在一位是1,而我们讨论的是高位连续的0的个数,如果区间第一位为1,那么第二位自然不用讨论。如果区间第一位是0,那么第二位必然是1,也不需要讨论。所以这里只用讨论第一位的情况
- formatUnsignedInt()
经过上面的以数字18为例的计算后,我们可以得到18的二进制高位会出现连续的 27个0, 接下来我们回到toUnsignedString()函数看程序.
//以18为例
private static String toUnsignedString0(int val, int shift) {
int mag = Integer.SIZE - Integer.numberOfLeadingZeros(val); // mag=5int chars = Math.max(((mag + (shift - 1)) / shift), 1); // chars=5char[] buf = new char[chars]; formatUnsignedInt(val, shift, buf, 0, chars);// Use special constructor which takes over "buf".return new String(buf, true);
}
它声明了buf字符数组,长度为 5. 接着调用 formatUnsignedInt( 18, 1, buf, 0, 5); 下面分析这个函数的源码:
// val=18 shift=1 offset=0 len=5
static int formatUnsignedInt(int val, int shift, char[] buf, int offset, int len) {
int charPos = len; // 5
int radix = 1 << shift; // 左移位,将运算数的二进制整体左移指定位数,低位用0补齐
//这样二进制的radix为 10(对应10进制为2) 八进制为 1000 (对应10进制为8) 十六进制为 1 0000(对应10进制为16)
int mask = radix - 1;
// mask为掩码
// 如要转为二进制时mask为1 转为八进制为7 转为十六进制为15
// 用二进制表示分别为 1 111 1111
do {
buf[offset + --charPos] = Integer.digits[val & mask];
// val & mask 相当于一个%(mask+1)运算,它计算出 val对应 mask 这个进制的余数,这个数正好可以对应到 digits中的下标,。 接着通过 Integer.digits取出它的值. 存到 buf数组对应位置
val >>>= shift; //再将 val 右移shift位,相当于 /(shift+1).
//以上对应的就是十进制转n进制的 模n取余法的算法实现了. } while (val != 0 && charPos > 0);return charPos;
}
以上调用了 Integer.digits 静态数组. 如下:
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’
};
Integer类的toBinaryString源码分析相关推荐
- StringBuffer类【JDK源码分析】
StringBuffer类[JDK源码分析] 前言 推荐 说明 StringBuffer类 基本信息 属性 构造方法 部分方法 length capacity append insert revers ...
- 通用Mapper Example类使用以及源码分析
目录 一.通用Mapper Example类使用 1. 常用使用方式举例 2. 使用方式:条件嵌套组合 二.通用Mapper Example类源码分析 1. 代码细节理解 1.1 Criteria类 ...
- JDK源码分析 FutureTask源码分析
文章目录 前言 一.Callable接口 二.Future接口 三.FutureTask源码分析 3.1 Future继承结构图 3.2 参数介绍 3.3 构造函数 3.4. FutureTask的A ...
- Android 别踩白块 源码分析
文章目录 一.项目下载 二.项目分析 三.源码分析 1.实体层(Entity) (1).Block类 [1].属性 [2].方法 (2).SuccGroup类 [1].属性 [2].方法 (3).Fa ...
- Android MultiDex 源码分析
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言 一.启用 MultiDex Android 5.0 和之后的版本 Android 5.0 之前的版本 二.MultiD ...
- JDK源码分析-Integer
Integer是平时开发中最常用的类之一,但是如果没有研究过源码很多特性和坑可能就不知道,下面深入源码来分析一下Integer的设计和实现. Integer: 继承结构: -java.lang.Obj ...
- 深入OKHttp源码分析(二)----OkHttp任务调度核心类Dispatcher解析
OkHttp任务调度核心类Dispatcher解析 上一篇我们分析了okhttp的同步和异步请求的执行流程并进行了源码分析,深入OKHttp源码分析(一)----同步和异步请求流程和源码分析 那么今天 ...
- 这篇文章绝对让你深刻理解java类的加载以及ClassLoader源码分析
前言 package com.jvm.classloader;class Father2{public static String strFather="HelloJVM_Father&qu ...
- java parseint 负号_java.lang.Integer#parseInt() 源码分析
Integer#parseInt() 是我们经常使用的一个函数, 是 Integer 类提供的一个静态工具方法, 其作用就是将字符串的数字转换为 int 类型. 一个更通用的 parseInt() 的 ...
最新文章
- jquery EasyUI导入js顺序
- java reflectionutils_ReflectionUtils工具类-装载
- 【Android 应用开发】分析各种Android设备屏幕分辨率与适配 - 使用大量真实安卓设备采集真实数据统计
- java调用net webservice_java调用.net的webservice
- js代码转python_Python和JavaScript间代码转换的4个工具
- python学习-递归(阶乘、汉诺塔)
- 想不到,那些让我半夜偷偷收藏的沙雕表情包,竟是出自AI之手
- 51 NOD 1363 最小公倍数之和 (欧拉函数思维应用)
- 【覆盖安装】通用测试点
- MySQL5添加外键约束错误 (Error Code : 1005)
- Docker组队学习(一)
- 进阶 09 Map集合
- EF中DataContext创建的两段代码收藏
- 百度AI实现图片转文字-python
- C语言飞机大战程序思路,C语言代码实现飞机大战
- Application.platform 平台
- matlab db dbm dbfs,dbfs(dbfs和dbm的换算)
- 有符号拓展:signed-extending无符号拓展:unsigned-extending
- 计算机网络信息中心研究生,计算机网络信息中心研究生招生常见问题答疑
- java版基础排序归并排
热门文章
- 如何合并多个(.txt或其他)文件到一个文件
- mysql数字连接,MySQL - 已达到数字连接
- vb combox获取选定index_Python-新闻评论获取
- 字符串匹配之KMP算法详解
- note_maven中的常用命令
- idea中tomcat服务器的配置
- Java中使用JNA实现全局监听Windows键盘事件
- html快捷保存图片,如何使用360浏览器快速保存图片
- 【NLP】毕设学习笔记(八)“前馈 + 反馈” = 循环神经网络RNN
- mysql markdown_mysql+数据库学习笔记(markdown)