探索Integer.toBinaryString源码

不解驱动着你

一般情况下我是不会主动去看源码的,除非是写专门的主题或者是遇到不懂的难题。果然了,于是带着好奇心尝试理解下源码,一会我先抛出问题,要是有同学一下子就明白了那就可以不用往下看了!还有一个前提就是最好对原码、补码、反码有所了解,因为计算机操作的数据就是以二进制的形式存在的,准确的说是用补码的形式来计算的!

    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就是最后的结果,最后在将其转成字符串输出。

结束语

源码在适当的情况下还是需要去看的,笔者正在一步一步往这里靠近,努力加强自己的硬实力!加油自己,加油每一个人!

上一篇:流水灯


下一篇:bilibiliclass26_C语言_数据的存储_原码、反码、补码_整形在内存中的存储