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,局部变量数=2,if 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;
诶,哪里不对,52429和524288你们俩个不对劲,有点眉目了。
再换种写法:
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;
}
}