经验,制造一切未来,经验,是所有过去的成果。而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
计算机里面所有数据都以二进制(补码)的形式存在,这里就是获取val在计算机内的二进制码长度,numberOfLeadingZeros表示高位全为0的个数, Integer.SIZE为32,代表Integer在java里面的4字节32位。mag就是除去高位全0的二进制位数。int mag = Integer.SIZE - Integer.numberOfLeadingZeros(val);
-
CODE-2
这里就是计算val待转为的进制的位数,也就是结果字符串的长度。shift代表待转进制中一位需要的二进制位数。int chars = Math.max(((mag + (shift - 1)) / shift), 1); (mag + (shift - 1))/shift
即mag / shift
向上取整。 (证明参照我另外一篇博客 【java面试题-算法】除法如何向上取整? 比如8进制shift为3,2进制有10位bit,chars就是10/3向上取整4 ) 。
-
CODE-3
初始化结果字符串buf。java中的字符串其实是一个char数组,java内置的编码方式是unicode的UTF-16。(想具体了解编码方式请看字符编码旧题新解,纠缠不清的(ASCII,GBK, GB2312,GB18030,UNICODE,UTF-8,UTF-16,UTF-32) )。char[] buf = new char[chars];
-
CODE-4
1往左移shift位实际上得到的是待转为进制各位权值的二进制表达。二进制权值为2 (shift=1 用10表示) , 八进制权值为8 (shift=3 用1000表示 ),十六进制权值为16 (shift=4 用10000表示) ,分别将其减1得到mask,即目标进制各位的最大数字,二进制为1 (1),八进制为7 (111),16进制为15 (1111)。int radix = 1 << shift;
int mask = radix - 1;
-
CODE-5
mask为目标进制各位的最大数字,do {
buf[offset + --charPos] = Integer.digits[val & mask];
val >>>= shift;
} while (val != 0 && charPos > 0);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。