java源码Integer类toBinaryString()方法探究

最近在准备实习的面试,准备从头梳理一下java的基本语法,也要开java源码的探究,学习前人优秀的代码。
在Integer类中有静态方法toBinaryString(int i)方法,此方法返回int变量的二进制表示的字符串。
同理,Integer类中也提供了toHexString(int i)方法和toOctalString(int i)方法来分别返回int变量的16进制表示和8进制表示字符串。
三个方法的源码分别如下:

  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(int val, int shift),源码如下:

  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);
    }

代码的主要流程是分为两步: 第一步,计算出用于表示二/八/十六进制的字符数组的长度;第二步,使用formatUnsignedInt方法填充字符数组,得到所需进制表示的字符串并返回。
第一步中字符数组长度的计算是通过numberOfLeadingZeros方法计算int变量的计算机二进制表示的高位连续0位的数量,进而获得最高非0位到最低位的长度,也就是需要表示的位数。比如整数18在计算机中的二进制存储为0000,0000,0000,0000,0000,0000,0001,0010,那么需要表示的部分便是1,0010,前面的28位均为0,不用表示。
如何获得变量二进制表示中高位连续0的个数呢? 如果我们自己思考这个问题,一般能给出的解决方案是通过判断高位是否为0,然后移位重复判断,记录连续的0的个数。根据这个思路我写了下面的方法:

    public static int numberOfLeadingZeros0(int i){
            if(i == 0)
                return 32;
            int n = 0;
            int mask = 0x80000000;
            int j = i & mask;
            while(j == 0){
                n++;
                i <<= 1;
                j = i & mask;
            }
            return n;
        }

很显然,这个方法的平均时间复杂度是o(k),k为变量i的位数,如果i为int,k就是32。看起来好像可以接受,但这种很基本的库函数会经过很多次的调用,需要进一步的优化。下面是java源码中的写法:

   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;
    }

对源码有几处研究:

  • 两处代码有几点不同,首先在判定某位是否为0时,后者采用了无符号右移然后与0比对的方式,前者采用了与0x80000000即2的-31次方进行与运算的结果与0比对的方式,后者一次判断多位连续为0的情况,而且更加简单不易出错。
  • 其次,前者从最高位,逐位进行判断;后者则通过划分16,8,4,2等逐步缩小的四个区间进行判断和移位。不管if的代码块是否执行,需要判断的位的区间也在随着代码的推进而逐步缩减。在我看来,这种方法是分治法的一种变体,通过分层次的缩小需要求解的域的范围,减少计算次数。这个算法思想十分优秀,可以将算法的平均时间复杂度降低到o( log2k )。但要注意,这种方法并不是在任何情况都能使用,它需要问题自身具备一定的条件。
    换个类似的问题,一个数组,数组中的元素只包含0和1,求最前面连续0的个数。
    对于这个问题,恐怕不能用上述方式求解,因为,上述方式可以通过i的无符号右移的结果与0比对的方式判断是否符合移动的所有位均为0的条件,但数组却没有这种方式,所以无符号右移的结果变量与0比对的方式是上述算法成立的必要条件。并不是所有类似的问题都有这种类似的方式,可以通过类似的解决方法来解决。
  • 由于需要进行判断分析的区间长度不断的对半减小,在经过了if (i >>> 30 == 0)之后,区间长度变成了2,但 n -= i >>> 31语句只讨论了区间为2的第一个位是否为0的情况,而没有考虑第二位的情况,为什么呢?
    因为,在方法的开始已经判断并返回了变量为0即所有的位均为0的情况,所以能进行到后面的代码部分,说明变量不为0,即必然存在至少一个位为1,而我们在选择需要讨论的位区间时,始终选择的是包含位为1的区间进行讨论。所以,在最后区间长度变为2时,区间中必然至少存在一位是1,而我们讨论的是高位连续的0的个数,如果区间第一位为1,那么第二位自然不用讨论。如果区间第一位是0,那么第二位必然是1,也不需要讨论。所以这里只用讨论第一位的情况

接下来讨论最后一个函数:

  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;
    }

我们就讨论一下int到char转化的方式,它是通过一个char[]实现从int到char的转化:

   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进制和8进制(16进制涉及字符为字母的情况,所以除外),可以通过
ASCII码进行int和char的转化,比如1到’1’可以通过char c = (char)(1+’0’)的方式进行转化,其中’0’的ASCII码是48,所以可以将’0’换成48。但我在将上述代码中val & mask的值转化成char时出现了错误,我的代码是char c = (char)(val & mask + 48),错误原因是操作符“&”与“+”优先级搞反了,“+”的优先级高于“&”,所以上式先执行了加法操作,后执行与操作,导致结果错误。

注意加减操作和位操作的优先级。

本人才疏学浅,文章难免会有纰漏,如有读者发现问题,欢迎提出,我会虚心更正。

  • 48
    点赞
  • 97
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值