Java:Integer源码心得

stringSize:数组和if else之争

stringSize是Integer内部获取数字位数的方法:

final static int [] sizeTable = { 9, 99, 999, 9999, 99999, 999999, 9999999,99999999,999999999, Integer.MAX_VALUE };
// Requires positive x
static int stringSize(int x) {
    for (int i=0; ; i++)
        if (x <= sizeTable[i])
            return i+1;
}

假设用if else代替数组:

// Requires positive x
    static int stringSizeIf(int x) {
        if (x <= 9) {
            return 1;
        } else if (x <= 99) {
            return 2;
        } else if (x <= 999) {
            return 3;
        } else if (x <= 9999) {
            return 4;
        } else if (x <= 99999) {
            return 5;
        } else if (x <= 999999) {
            return 6;
        } else if (x <= 9999999) {
            return 7;
        } else if (x <= 99999999) {
            return 8;
        } else if (x <= 999999999) {
            return 9;
        } else {
            return 10;
        }
    }

好像也没有那么大区别,if else具有更大的可读性,不是吗?
对比下编译后的class文件
数组版

// access flags 0x8
  static stringSize(I)I
   L0
    LINENUMBER 11 L0
    ICONST_0
    ISTORE 1
   L1
    LINENUMBER 12 L1
   FRAME APPEND [I]
    ILOAD 0
    GETSTATIC com/goyoung/jvm/IntegerTest.sizeTable : [I
    ILOAD 1
    IALOAD
    IF_ICMPGT L2
   L3
    LINENUMBER 13 L3
    ILOAD 1
    ICONST_1
    IADD
    IRETURN
   L2
    LINENUMBER 11 L2
   FRAME SAME
    IINC 1 1
    GOTO L1
   L4
    LOCALVARIABLE i I L1 L4 1
    LOCALVARIABLE x I L0 L4 0
    MAXSTACK = 3
    MAXLOCALS = 2

if else版

static stringSizeIf(I)I
   L0
    LINENUMBER 18 L0
    ILOAD 0
    BIPUSH 9
    IF_ICMPGE L1
   L2
    LINENUMBER 19 L2
    ICONST_1
    IRETURN
   L1
    LINENUMBER 20 L1
   FRAME SAME
    ILOAD 0
    BIPUSH 99
    IF_ICMPGE L3
   L4
    LINENUMBER 21 L4
    ICONST_2
    IRETURN
   L3
    LINENUMBER 22 L3
   FRAME SAME
    ILOAD 0
    SIPUSH 999
    IF_ICMPGE L5
   L6
    LINENUMBER 23 L6
    ICONST_3
    IRETURN
   L5
    LINENUMBER 24 L5
   FRAME SAME
    ILOAD 0
    SIPUSH 9999
    IF_ICMPGE L7
   L8
    LINENUMBER 25 L8
    ICONST_4
    IRETURN
   L7
    LINENUMBER 26 L7
   FRAME SAME
    ILOAD 0
    LDC 99999
    IF_ICMPGE L9
   L10
    LINENUMBER 27 L10
    ICONST_5
    IRETURN
   L9
    LINENUMBER 28 L9
   FRAME SAME
    ILOAD 0
    LDC 999999
    IF_ICMPGE L11
   L12
    LINENUMBER 29 L12
    BIPUSH 6
    IRETURN
   L11
    LINENUMBER 30 L11
   FRAME SAME
    ILOAD 0
    LDC 9999999
    IF_ICMPGE L13
   L14
    LINENUMBER 31 L14
    BIPUSH 7
    IRETURN
   L13
    LINENUMBER 32 L13
   FRAME SAME
    ILOAD 0
    LDC 99999999
    IF_ICMPGE L15
   L16
    LINENUMBER 33 L16
    BIPUSH 8
    IRETURN
   L15
    LINENUMBER 34 L15
   FRAME SAME
    ILOAD 0
    LDC 999999999
    IF_ICMPGE L17
   L18
    LINENUMBER 35 L18
    BIPUSH 9
    IRETURN
   L17
    LINENUMBER 37 L17
   FRAME SAME
    BIPUSH 10
    IRETURN
   L19
    LOCALVARIABLE x I L0 L19 0
    MAXSTACK = 2
    MAXLOCALS = 1

对比发现:
1、数组版的字节码指令,远远小于if else版
2、数组版操作数栈深度=3,局部变量数=2if else版操作数栈深度=2,局部变量数=1数组版if else版占用了更多的内存,往小了说只大1,往大了说差50%【手动狗头】。
3、数组版数组使用final修饰,编译期数组的值就会固定下来,且随着类的加载一同加载到方法区中,这时候数组占用的内存很难被回收掉,if else版不存在这个问题。

如何取舍?装逼来说,肯定数组版合适。

为什么是52429

static void getChars(int i, int index, char[] buf) {
        int q, r;
        int charPos = index;
        char sign = 0;

        if (i < 0) {
            sign = '-';
            i = -i;
        }

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

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

这段代码中有一个很特别的数字52429

q = (i * 52429) >>> (16+3);

为什么是52429,我们知道正整数在二进制表示下,向右移一位,表示除以2,右移16+3位呢?

219 = 524288

给代码转换下形式

q = (i * 52429) / 524288;

诶,哪里不对,52429524288你们俩个不对劲,有点眉目了。
再换种写法:

q = i * (52429 / 524288);
q = i * 0.1000003814697265625;

到这里我们已经知道怎么回事了,这行代码实际上进行了一次除10运算。cpu做乘法比除法高效,所以这里用乘法代替了除法。

Integer的内部缓存

Integer内部添加了对数据的缓存,缓存任务由私有内部类IntegerCache完成。
当我们调用Integer.valueOf()方法时

public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

首先判断参数是否在缓存范围内,在范围内则返回缓存的实例。
IntegerCache内部定义了缓存范围,最小值是-128:

private static class IntegerCache {
        static final int low = -128;
}

最大值取决于Jvm的设置值与127比较的结果:

private static class IntegerCache{
	    static final int high;
		static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;
       }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值