jdk源码:Integer.getChars(int i, int index, char[] buf)

1. 应用:将整形数字转换成对应的十进制字符串

    public static String toString(int i) {
        if (i == Integer.MIN_VALUE)
            return "-2147483648";
        int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
        char[] buf = new char[size];
        getChars(i, size, buf);
        return new String(buf, true);
    }

2. 测试代码

    public static void main(String[] args) {
        System.out.println(Integer.toString(-2147483647));
        System.out.println(Integer.toString(2147483647));
        System.out.println(Integer.toString(66580));
    }

执行结果:

-2147483647
2147483647
66580

3. 源码分析: getChars(int i, int index, char[] buf)

  • 源码
    /**
     * Places characters representing the integer i into the
     * character array buf. The characters are placed into
     * the buffer backwards starting with the least significant
     * digit at the specified index (exclusive), and working
     * backwards from there.
     *
     * Will fail if i == Integer.MIN_VALUE  
     */
    static void getChars(int i, int index, char[] buf) {
        // 代码段1
        int q, r;
        int charPos = index;
        char sign = 0;

        if (i < 0) {
            sign = '-';
            i = -i;
        }
        // 代码段2
        // Generate two digits per iteration
        while (i >= 65536) {
            q = i / 100;
        // really: r = i - (q * 100);
            r = i - ((q << 6) + (q << 5) + (q << 2));
            i = q;
            buf [--charPos] = DigitOnes[r];
            buf [--charPos] = DigitTens[r];
        }

        // 代码段3
        // Fall thru to fast mode for smaller numbers
        // assert(i <= 65536, i);
        for (;;) {
            q = (i * 52429) >>> (16+3);
            r = i - ((q << 3) + (q << 1));  // r = i-(q*10) ...
            buf [--charPos] = digits [r];
            i = q;
            if (i == 0) break;
        }
        if (sign != 0) {
            buf [--charPos] = sign;
        }
    }
  1. 此方法作用将int数字转换放进一个字符数组
  2. 在数字为int最小值时,此方法会失败
    以上是此方法注释的内容,源码主要是将数字i转换为字符串,方法有三段组成。
  3. 代码段1主要判断i的正负,并对负数取反,方便处理。这里也解释了为什么当i为最小数字时方法失败,因为int范围是-2147483648~2147483647,当其取反时超过了int的范围。
  4. 代码段2是处理i>=65536时,每次取数字i的最后两位转为字符
while (i >= 65536) {
        q = i / 100;
        // really: r = i - (q * 100);
        r = i - ((q << 6) + (q << 5) + (q << 2));
        i = q;
        //取DigitOnes[r]的目的其实取数字r%10的结果
        buf [--charPos] = DigitOnes[r];
        //取DigitTens[r]的目的其实是取数字r/10的结果
        buf [--charPos] = DigitTens[r];
    }

这个代码很简单,我们看懂两个地方就行了。

  • a. r = i - ((q << 6) + (q << 5) + (q << 2)); 实际上去 i - q*100,得到r是i的最后两位
  • b. buf [--charPos] = DigitOnes[r]; buf [--charPos] = DigitTens[r];巧妙的运用两个数组查找,避免除法等计算。
  1. 代码段3是处理i<65536时每次取一位转为字符
for (;;) {
        //这里其实就是除以10。取数52429和16+3的原因在后文分析。
        q = (i * 52429) >>> (16+3);
        r = i - ((q << 3) + (q << 1));  // r = i-(q*10) ...
        //将数字i的最后一位存入字符数组,
        buf [--charPos] = digits [r];
        i = q;
        //for循环结束后,buf内容为“12345678”;
        if (i == 0) break;
    }

这个代码我们需要知道

  • a. q = (i * 52429) >>> (16+3) 约等于 i/10
  • b. r = i - ((q << 3) + (q << 1)),其实是r = i - q*10, 所以r其实是i的最后一位,然后放进字符数组

但是这里我们肯定会有一些疑惑:
问题1:为什么是65536,为什么要在大于等于65536时和小于65536时采用不同的处理方法。
问题2:代码段3中 q = (i * 52429) >>> (16+3);中52439,16+3是怎么确定出来的
num1 = 65536, num2 = 52429, num3 = 19
在了解这num1,num2,num3为什么是这三个数字之前,我们有几个需要了解的前提:
1.移位效率高于乘除
2.乘法效率高于除法
这时我们注意到,代码段2里采用了除法和移位,代码段3里采用了乘法和移位。理论上我们如果都是用代码段3进行处理效率会更高,但是注意到代码段3有这样一个操作是需要我们学习的。

q = (i * 52429) >>> (16+3);  // 约等于i/10,这里巧妙的运用了乘法和移位避免使用除法来提高效率。

这里巧妙的运用了乘法和移位避免使用除法来提高效率,但是同时也要求(i * 52429)不能超过整形的范围。有符号整形是-2147483648至2147483647,无符号是0-4,294,967,296(2^32),由于后面的计算是采用无符号右移,所以我们只需要使得(i * 52429)不超过232就可以了。所以说这里的i理论上是不能超过232除以52429,这就解释了为什么要分i>=num1和i<num1来处理。

好,这样也就解释了num1,num2,num3这三个数的来历。那怎么确定这三个数的值,我们需要进行一些计算。由于q = (i * 52429) >>> (16+3); // 约等于i/10,所以我们得出num2/2^num3=0.1 => num2=2^num3/10
最终num2=2^num3/10+1,最后的+1应该是未了再计算的时候避免被向下取整了吧。这里我有一个测试代码证明了这个猜想

public static void main(String[] args) {
        System.out.println((1020 * 52428) >>> (16+3));  // 理论上精确的除以10,在计算机计算过程中可能会产生向下取整导致结果不准确的问题
        System.out.println((1020 * 52429) >>> (16+3));  // 改进版
    }

运行结果

101
102

回到主题,根据以上关系我们可以得出多组num2,num3

2^10=1024, 103/1024=0.1005859375
2^11=2048, 205/2048=0.10009765625
2^12=4096, 410/4096=0.10009765625
2^13=8192, 820/8192=0.10009765625
2^14=16384, 1639/16384=0.10003662109375
2^15=32768, 3277/32768=0.100006103515625
2^16=65536, 6554/65536=0.100006103515625
2^17=131072, 13108/131072=0.100006103515625
2^18=262144, 26215/262144=0.10000228881835938
2^19=524288, 52429/524288=0.10000038146972656
2^20=1048576, 104858/1048576=0.1000003815
2^21=2097152, 209716/2097152 = 0.1000003815
2^22= 4194304, 419431/4194304= 0.1000001431
2^23= 8388608, 838861/8388608= 0.1000000238
...

接下来其实有一个临界值((num1-1)num2) >>> num3,需要保证((num1-1)num2) 不能超过2^32。所以就是num1越大,num2就越小;num2越大,num1就越小。但同时这里我们有两个诉求:

  1. 由于上面是约等于i/10,所以我们得对精度有要求。精度必须保证,可以得出num2和num3越大,精度就越高
  2. 因为乘法小于高于除法,我们偏向于num1越大越好,这样尽量多使用代码段3

从上面两个诉求来看,我们的num1,num2,num3都是越大越好。但是((num1-1)*num2) 不能超过2^32也告诉了我们num1和num2一个大了另一个就得小。所以采取折中的方式,精度足够了就好,乘除法效率上我们也妥协一些。

我们发现,当num3=19时,精度达到了0.10000038146972656,0.1后面是5个0,我们称精度达到了5,这个精度可以说符合要求了已经。并且当num3=20,21,22时精度都保持在5,此时提升num3和num2对精度并没有提升。所以num3我们选择了19, num2也顺理成章为52429, 在2^15 和 2^16之间。同时((num1-1)*num2) 不能超过232,所以num1选择了216。

这里笔者要吐槽一下网上对于这三个值的一些解释,自己都不能说服自己

  1. 下面这种说法真真看不懂,可能是我没理解到位,下一个精度较高的组合是419431和22。当num值为19,20,21,22时的精度不是一样高的吗,为什么下一个是22。 另外“231/222 = 2^9 = 512。512这个数字实在是太小了”实在是看不懂啊。
52429/524288=0.10000038146972656精度足够高
下一个精度较高的m和n的组合是419431和22。2^31/2^22 = 2^9 = 512。512这个数字实在是太小了。65536正好是2^16,一个整数占4个字节。65536正好占了2个字节,选定这样一个数字有利于CPU访问数据。
  1. 下面这种说法更是可笑,你说不超出整形范围内值得是((num1-1)*num2)不超过吧。你这么说应该是已经认定了num1=65536,可是num1,num2,num3这三个值的确定本就是互相影响,互相确定的。 你这么说是在num1=65536的大前提下的,可是num1的值时怎么确定的呢,对吧?
至于为什么选择2的19次方524288,是因为52429/524288得到的数字精度在不超出整形范围内,精度是最高的。

4. 对两个数组的解释

100以下的数,DigitTens十位数上的数字,DigitOnes为个位数的数字,如86 =DigitTens[86]+ DigitOnes[86] = '8' + '6' = 86

比较巧妙的设计

//100以内的数字除以10的结果(取整)
final static char [] DigitTens = {
    '0', '0', '0', '0', '0', '0', '0', '0', '0', '0',
    '1', '1', '1', '1', '1', '1', '1', '1', '1', '1',
    '2', '2', '2', '2', '2', '2', '2', '2', '2', '2',
    '3', '3', '3', '3', '3', '3', '3', '3', '3', '3',
    '4', '4', '4', '4', '4', '4', '4', '4', '4', '4',
    '5', '5', '5', '5', '5', '5', '5', '5', '5', '5',
    '6', '6', '6', '6', '6', '6', '6', '6', '6', '6',
    '7', '7', '7', '7', '7', '7', '7', '7', '7', '7',
    '8', '8', '8', '8', '8', '8', '8', '8', '8', '8',
    '9', '9', '9', '9', '9', '9', '9', '9', '9', '9',
    } ;
//100以内的数字对10取模的结果
final static char [] DigitOnes = {
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    } ;

5. 从这个方法我们学到的

  • 乘法比除法高效:q = ( i * 52429) >>> (16+3); => 约等于q0.1,但i52429是整数乘法器,结合位移避免除法
  • 充分利用计算结果:在获取r(i%100)时,充分利用了除法的结果,结合位移避免重复计算
  • 位移比乘法高效:r = i – (( q << 6) + ( q << 5) + ( q << 2)); = >等价于r = i – (q * 100)
  • 局部性原理之空间局部性
(1).buf[–charPos] =DigitOnes[r];buf[–charPos] =DigitTens[r];通过查找数组,实现快速访问,避免除法计算
(2).buf [–charPos ] = digits [ r];

作者 @没有故事的老大爷
奋斗,但是该不该知足,该不该休息呢?

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,928评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,192评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,468评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,186评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,295评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,374评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,403评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,186评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,610评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,906评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,075评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,755评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,393评论 3 320
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,079评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,313评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,934评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,963评论 2 351

推荐阅读更多精彩内容

  • The Integer class wraps a value of the primitive type int...
    0x70e8阅读 531评论 0 0
  • 0.linux启动的步骤 设备加电----》BIOS自检-----》grub引导启动-----》加载内核----》...
    萌面大叔2阅读 774评论 0 1
  • 〇、前言 本文共108张图,流量党请慎重! 历时1个半月,我把自己学习Python基础知识的框架详细梳理了一遍。 ...
    Raxxie阅读 18,941评论 17 410
  • 这节课我们来做一个加法计算器,也就是输入2个数字,让程序计算出结果。 直接在python环境输入代码并运行 打开“...
    学哥量化交易学习阅读 3,509评论 2 3
  • 358期 3489=1 012567=2
    司令6789阅读 206评论 0 0