JDK原码研究: java.lang.Integer打印二进制,八进制,十六进制字符串

经验,制造一切未来,经验,是所有过去的成果。而jdk源码是优秀的经验。

Integer.toBinaryString用于打印整形Integer的二进制字符串,类似还有toOctalString打印八进制字符串,toHexString打印十六进制。与基于10进制转向二进制,八进制,十六进制的过程 将10进制的数字除以二,每次除得的余数保存起来,一直除到最后商小于1,代码结尾进行拼接再输出 不同。JDK用更加直观简洁的方法快速求得。

以Integer.toBinaryString为例,其调用toUnsignedString0(int val, int shift),val为待转的整数,shift是进制数各位的权值用bit表示所需要的bit数量 2进制shift=1,8进制shift=3,16进制shift=4。其流程是首先确定字符串长度,然后确定字符串每个位置的值。

toUnsignedString0里面有2个方法,numberOfLeadingZeros确定整数补码数字高位为0的数量。formatUnsignedInt确定每个位置的字符。

源码

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

private static String toUnsignedString0(int val, int shift) {
    
    
    /* 分析 code-1 */
    int mag = Integer.SIZE - Integer.numberOfLeadingZeros(val);
    /* 分析 code-2 */
    int chars = Math.max(((mag + (shift - 1)) / shift), 1);
    /* 分析 code-3 */
    char[] buf = new char[chars];
    
    /* 把计算好的字符推如buf中 */
    formatUnsignedInt(val, shift, buf, 0, chars);

    /* 返回结果String */
    return new String(buf, true);
}


static int formatUnsignedInt(int val, int shift, char[] buf, int offset, int len) {
    
    
    /* len就是字符串长度 */
    int charPos = len;
    
    /* 分析 code-4 */
    int radix = 1 << shift;
    int mask = radix - 1;
    
    /* 分析 code-5 */
    do {
    
    
        buf[offset + --charPos] = Integer.digits[val & mask];
        val >>>= shift;
    } while (val != 0 && charPos > 0);

    return charPos;
}   

分析

  • CODE-1

    int mag = Integer.SIZE - Integer.numberOfLeadingZeros(val);
    计算机里面所有数据都以二进制(补码)的形式存在,这里就是获取val在计算机内的二进制码长度,numberOfLeadingZeros表示高位全为0的个数, Integer.SIZE为32,代表Integer在java里面的4字节32位。mag就是除去高位全0的二进制位数。在这里插入图片描述

  • CODE-2

    int chars = Math.max(((mag + (shift - 1)) / shift), 1);
    这里就是计算val待转为的进制的位数,也就是结果字符串的长度。shift代表待转进制中一位需要的二进制位数。(mag + (shift - 1))/shiftmag / shift向上取整。 证明参照我另外一篇博客 【java面试题-算法】除法如何向上取整? 比如8进制shift为3,2进制有10位bit,chars就是10/3向上取整4

  • CODE-3

    char[] buf = new char[chars];
    初始化结果字符串buf。java中的字符串其实是一个char数组,java内置的编码方式是unicode的UTF-16。想具体了解编码方式请看字符编码旧题新解,纠缠不清的(ASCII,GBK, GB2312,GB18030,UNICODE,UTF-8,UTF-16,UTF-32)

  • CODE-4

    int radix = 1 << shift;
    int mask = radix - 1;
    1往左移shift位实际上得到的是待转为进制各位权值的二进制表达。二进制权值为2 (shift=1 用10表示) , 八进制权值为8 (shift=3 用1000表示 ),十六进制权值为16 (shift=4 用10000表示) ,分别将其减1得到mask,即目标进制各位的最大数字,二进制为1 (1),八进制为7 (111),16进制为15 (1111)

  • CODE-5

    do {
      buf[offset + --charPos] = Integer.digits[val & mask];
      val >>>= shift;
    } while (val != 0 && charPos > 0);
    mask为目标进制各位的最大数字, val & mask表示就是val转为目标进制低位的值, digits是个存了[0-z]共36个字符的数组,下标0对应’0’,1对应’1’,15对应’F’。

    于是以val & mask为下标取得digit字符为val的目标进制的低位字符。 val >>>= shift 相当于value=value>>>shift。从低到高,每获取一位,将位置再往右边移shift 代表待转进制中一位需要的二进制位数 。 逐字节判断。然后将结果放入buf中。

    比如val为10进制的9,其二进制表达式为1001,mask为111,digit[1001& 111] = digit[1] = '1'。然后1001 >>>= 3 = 1。 最后执行digit[1&111] = digt[1] = '1'。所以9的八进制表达式为11。

猜你喜欢

转载自blog.csdn.net/kiramario/article/details/115503141