不解驱动着你
一般情况下我是不会主动去看源码的,除非是写专门的主题或者是遇到不懂的难题。果然了,于是带着好奇心尝试理解下源码,一会我先抛出问题,要是有同学一下子就明白了那就可以不用往下看了!还有一个前提就是最好对原码、补码、反码有所了解,因为计算机操作的数据就是以二进制的形式存在的,准确的说是用补码的形式来计算的!
public class URShift {
public static void main(String[] args) {
int i = -1;
System.out.println("①:" + Integer.toBinaryString(i));
i >>>= 10;
System.out.println("②:" + Integer.toBinaryString(i));
long l = -1;
System.out.println("③:" + Long.toBinaryString(l));
l >>>= 10;
System.out.println("④:" + Long.toBinaryString(l));
short s = -1;
System.out.println("⑤:" + Integer.toBinaryString(s));
s >>>= 10;
System.out.println("⑥:" + Integer.toBinaryString(s));
byte b = -1;
System.out.println("⑦:" + Integer.toBinaryString(b));
b >>>= 10;
System.out.println("⑧:" + Integer.toBinaryString(b));
}
}
①:11111111111111111111111111111111
②:1111111111111111111111
③:1111111111111111111111111111111111111111111111111111111111111111
④:111111111111111111111111111111111111111111111111111111
⑤:11111111111111111111111111111111
⑥:11111111111111111111111111111111
⑦:11111111111111111111111111111111
⑧:11111111111111111111111111111111
源码
Integer.toBinaryString
内部实现的方法主要有两个:numberOfLeadingZeros
formatUnsignedInt
,剩下的就无关紧要了,所以接下来的内容主要围绕着两个方法来讲。
public static int numberOfLeadingZeros(int i) {
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;
}
开始之前在复习下计算机的原码、补码、反码的相关计算,举个简单的例子。
数值 | 原码 | 反码 | 补码 |
---|---|---|---|
1 | 0000 0000 0000 0000 0000 0000 0000 0001 | 0000 0000 0000 0000 0000 0000 0000 0001 | 0000 0000 0000 0000 0000 0000 0000 0001 |
-1 | 1000 0000 0000 0000 0000 0000 0000 0001 | 1111 1111 1111 1111 1111 1111 1111 1110 | 1111 1111 1111 1111 1111 1111 1111 1111 |
为了方便理解,我们采用假设的方式来引导读者。针对numberOfLeadingZeros
方法假设入参i = 1
,以下分多个步骤来分析该方法,在分析方法之前先跟读者说下该方法的主要作用是:获取该二进制从左侧开始数连续0的个数
,有了这个个数就能构建数组的大小去存储有效的数据,二进制中前置位为0的话是不会存储的。
-
① 判断i是否等于0,结果很明显,此时
i = 1,n = 1
; -
② 判断i左移16位后是否等于0,计算如下:
数值 | 计算前 | 移位 | 计算后 |
---|---|---|---|
1 | 0000 0000 0000 0000 0000 0000 0000 0001 | 左移16 | 0000 0000 0000 0000 0000 0000 0000 0000 |
很显然,结果是等于0,故此时n = 17
,而i又做了计算,如下:
数值 | 计算前 | 移位 | 计算后 |
---|---|---|---|
1 | 0000 0000 0000 0000 0000 0000 0000 0001 | 右移16 | 0000 0000 0000 0001 0000 0000 0000 0000 |
-
③
不管是byte还是short类型,它们在移位之前都会被转换成int类型,再进行计算,所以它们的位数也是32位
。好奇的地方来了,为啥要左移16位呢,而相等后又要右移16位呢?一开始我也不太懂要这么做,后面看了一片文章觉得应该跟算法有关系,准确的说用的是二分法
,就比如有10位数字,利用10位的一半5来做为开头,而后做完逻辑后又取剩下位数的一半来继续做逻辑运算,直接结束。有了以上的两个前提,所以先左移16位,若等于0的话,则说明该二进制左侧至少有连续的16个0
,相当于32位中的前16位已经计算好了,接下来就应该计算剩下的16位,因为目前连续的0个数是至少有16位,还有剩下16位不知道情况,所以有可能有更多的0连续。那么问题来了,怎么才能知道后16位的情况呢?准确的来说应该是怎么知道后16位有多少个连续0,这就很明确了,直接计算后16位等于多少不就可以知道结果了吗?是的,就是这么回事,利用二分法,应该是后16位中取前8位来计算,也就是 0000 0000 0000 0000 0000 0000 0000 0001 计算下划线的数字中包含几位连续的0,一般想的话直接会把上面下划线中的数字直接右移8位即可得到结果,可以是可以,只不过写的代码会有很多重复,你要判断多个if-else,而if里头又有if-else,看着很恶心,所以还是算法吊,这边的右移16位是为了跟下一个判断做一个对应。 -
④ 判断i左移24位后是否等于0,相当于计算上面提到的下划线中的数据内容,计算如下:
数值 | 计算前 | 移位 | 计算后 |
---|---|---|---|
1 | 0000 0000 0000 0001 0000 0000 0000 0001 | 右移24 | 0000 0000 0000 0000 0000 0000 0000 0000 |
结果等于0,此时n = 25
,而i又做了计算,如下:
数值 | 计算前 | 移位 | 计算后 |
---|---|---|---|
1 | 0000 0000 0000 0001 0000 0000 0000 0001 | 右移24 | 0000 0001 0000 0000 0000 0000 0000 0000 |
相当于准备要计算最后的8位了,那么就看下一个判断语句了。
-
⑤ 判断i左移28位,这边就不在一一做运算了,结果
n = 29
。 -
⑥ 判断i左移30,结果
n = 31
。
数值 | 结果值 |
---|---|
1 | 0100 0000 0000 0000 0000 0000 0000 0000 |
- ⑦ 计算 n = n - i >>> 31,i右移31位后等于0,所以n最终等于31位,刚好说明从左侧开始有连续的31个0。
接下来讨论另外一个方法formatUnsignedInt
,紧接着上面假设后得出的结果,所以mag = 1
,表示该数值的有效位是1位,故将会构建长度为1的数组,对于任意数据,最小的长度就是1,而对于最大的长度就要看具体的数值了。好了,formatUnsignedInt
方法中入参为val = 1, shift = 1, buf = new char[1], offset = 0, len = 1
。在分析之前先说下该方法的主要作用,其实也没啥可说的,就是将数组解析成二进制后并放入到数组中
。
-
①
charPos = 1
表示有效位的个数,radix = 2
表示要转换成二进制的基数,若是要转换后八进制,则radix=8,同理,十六进制radix=16,不过你们也知道二进制的数只有0、1,同理就不阐述其他进制了,mask = 1
,一般我们要将十进制转换成其他进制的话,只需要&(与)上对应的进制基数即可得到结果,就好比是八进制的话就&7、十六进制的话就&15,这是最快得到十六进制表示的数值。 -
② 由于要将数组转换成二进制,故&1,根据得到的索引值查找数组当中对应的某个值,然后在数组的最高位上进行存储,相当于越低位应该放在越高位上,正好与二进制的格式相对应,相信不难理解。
-
③ 计算完上一个数后,开始右移动,对于八进制的话应该是右移3位,因为3位数表示1位八进制,同理,对于十六进制应该是右移4位,因为4位数表示1位十六进制,这边是二进制,故右移1位。
-
④ 判断移位后数值是否等于0,毕竟等于0的话咱们就没必要计算了,所以结果会依次循环计算该数值并移位,直到最后。
-
⑤ 每次计算都是把计算结果放到数组当中去,所以
buf
就是最后的结果,最后在将其转成字符串输出。
结束语
源码在适当的情况下还是需要去看的,笔者正在一步一步往这里靠近,努力加强自己的硬实力!加油自己,加油每一个人!