1、Integer源码分析
包装类的继承图如下:
1.1、Number
Number:当前类型的值怎么转化为其他类型的值
public abstract class Number implements Serializable {
private static final long serialVersionUID = -8742448824652078965L;
public Number() {
}
public abstract int intValue();
public abstract long longValue();
public abstract float floatValue();
public abstract double doubleValue();
public byte byteValue() {
return (byte)this.intValue();
}
public short shortValue() {
return (short)this.intValue();
}
}
1.2、包装类的缓存机制
Integer中有一个内部类IntegerCache,通过静态代码块创建了一个[-128,127]的Integer数组,在执行自动装箱的时候,会判断当前值是否在[-128,127]的区间内,如果在,则从缓存中直接获取
public final class Integer extends Number implements Comparable<Integer> {
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
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;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
}
1.3、Integer由于缓存机制出现的"诡异"现象
你可能常看到如下代码:
public static void main(String[] args) {
Integer a = 10 ;
Integer b = 10 ;
Integer c = 150 ;
Integer d = 150 ;
Integer e = new Integer(10) ;
Integer f = new Integer(10) ;
Integer g = new Integer(150);
Integer h = new Integer(150);
System.out.println(a == b );
System.out.println(c == d );
System.out.println(e == f);
System.out.println(g == h);
System.out.println(g.equals(h));
}
输出结果如下:
true
false
false
false
true
原因:
- a == b —> 自动装箱,并且在[-128 , 127] 区间内走了缓存(Integer[] cache)
- c != d —> 自动装箱,不在[-128 , 127] 区间内,未走缓存,new 的一个新对象
- e != f , g != h —> 每次new都是创建新对象,并没有走缓存,== 比较的是地址,当然不一样
- g.equals(h) == true —> Integer重写了 equals()方法,所以要通过equals比较,而非 ==
补充:
// 构造方法可以看到重新创建的对象,未走缓存
public Integer(int value) {
this.value = value;
}
// Integer重写了 equals()方法,所以要通过equals比较,而非 ==
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
1.4、自动装箱和拆箱
自动装箱原理:
自动装箱:是将基本类型转化为引用类型,反编译的字节码会调用valueOf()方法 ,这个方法会判断值是否在[-128,127]区间内,如果在会在Integer[] cache缓存中取,否则就创建一个Integer的实现类
public static void main(String[] args) {
//将基本类型转化为引用类型
Integer a = 10;
}
反编译字节码如下:
valueOf()源代码如下:
public static Integer valueOf(int var0) {
return var0 >= -128 && var0 <= Integer.IntegerCache.high
? Integer.IntegerCache.cache[var0 + 128] : new Integer(var0);
}
自动拆箱原理:
自动拆箱:是将引用类型转化为基本类型,反编译的字节码会调用intValue方法,这个方法会返回Integer对象的value属性值
public static void main(String[] args) {
int b = new Integer(20) ;
}
反编译之后的字节码:
intValue()源代码如下:
public int intValue() {
return this.value;
}
1.5、digits数组
digits数组: 根据不同进制进行转换时,涉及的所有字符,int支持二进制到三十六进制(故需要36个字符)
比如说转16进制可能出现的字符为0~e
public final class Integer extends Number implements Comparable<Integer> {
final static char[] digits = {
'0' , '1' , '2' , '3' , '4' , '5' ,
'6' , '7' , '8' , '9' , 'a' , 'b' ,
'c' , 'd' , 'e' , 'f' , 'g' , 'h' ,
'i' , 'j' , 'k' , 'l' , 'm' , 'n' ,
'o' , 'p' , 'q' , 'r' , 's' , 't' ,
'u' , 'v' , 'w' , 'x' , 'y' , 'z'
};
}
1.6、sizeTable数组
sizeTable数组:用来计算将Inteter转化为字符串时,求字符串的长度,通过使用9,99,999,到Integer的最大值,表示一个个的区间,比如你的值在[0,9],说明转化之后的字符串的长度就是1,值在[10,99],转化之后长度就是2
public final class Integer extends Number implements Comparable<Integer> {
final static int [] sizeTable = { 9, 99, 999, 9999, 99999, 999999, 9999999,
99999999, 999999999, Integer.MAX_VALUE };
static int stringSize(int x) {
//判断是否在[0,9] --> i+1 --> 1
//判断是否在[10,99] --> i+1 --> 2
// ...
for (int i=0; ; i++)
if (x <= sizeTable[i])
return i+1;
}
}
在求数字的长度的时候,还可以:
- int a = 123;
- String b = a+“” ;
- b.length() —> 拿到长度
1.7、DigitTens、DigitOnes数组
DigitTens、DigitOnes数组主要用来将数字转化为String的
- DigitOnes:表示个位数
- DigitTens: 表示十位数
为什么只需要表示个位数和十位数呢?得先看转换思路:
当 i >= 65536时,是每次取出后两位数字,i /= 100,例如 i = 567235474,
(1)先取最后两位 7 和 4 放入buf数组中,i = 5672354,buf = { , , , , , , , ‘7’, ‘4’};
(2)再取最后两位 5 和 4 放入buf数组中,i = 56723,buf = { , , , , , ‘5’, ‘4’, ‘7’, ‘4’};
当 i < 65536 时,跳出循环,采用每一次取出一位数字,也就是 i /= 10
(3)取最后一位 3 放入buf数组中,i = 5672,buf = { , , , , ‘3’, ‘5’, ‘4’, ‘7’, ‘4’};
(4)取最后一位 2 放入buf数组中,i = 567,buf = { , , , ‘2’, ‘3’, ‘5’, ‘4’, ‘7’, ‘4’};
(5)取最后一位 7 放入buf数组中,i = 56,buf = { , , ‘7’, ‘2’, ‘3’, ‘5’, ‘4’, ‘7’, ‘4’};
(6)取最后一位 6 放入buf数组中,i = 5,buf = { , ‘6’, ‘7’, ‘2’, ‘3’, ‘5’, ‘4’, ‘7’, ‘4’};
(7)取最后一位 5 放入buf数组中,i = 0,buf = { ‘5’, ‘6’, ‘7’, ‘2’, ‘3’, ‘5’, ‘4’, ‘7’, ‘4’},结束。
因为最多对100取余:所以能表示出来个位数和十位数即可,DigitOnes、DigitTens核心作用就是拿到目标数字i对100取余之后,余数m,将m带入两个数组中就能拿到m的个位和十位,设计很巧妙。
eg : i = 567235474 --》 m = i % 100 --》 m = 74 --》 DigitOnes[74] = 4 , DigitTens[74] = 7
public final class Integer extends Number implements Comparable<Integer> {
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',
} ;
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',
} ;
public static String toString(int i) {
if (i == Integer.MIN_VALUE)
return "-2147483648";
//先求 Integer --> String , 字符串长度
int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
//buf --> 存储字符串需要的长度
char[] buf = new char[size];
//将i --》 转成字符串存到buf中
getChars(i, size, buf);
return new String(buf, true);
}
/**
* 将i转化为字符串存在buf中
* i : 要转换的数字
* index : 从第几位开始转换的后一个, 从个位开始向前转换
* buf : 转换的数字存储在buf中
*/
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) {
//保存除以100之后的数字
q = i / 100;
//really: r = i - (q * 100); ---》 i %100
//等同于i对100取余
r = i - ((q << 6) + (q << 5) + (q << 2));
i = q;
//charPos是可用位置的后一个,所以charPos先减在用
//DigitOnes拿到r个个位 , DigitTens拿到r的十位
//eg : r == 74 --> DigitOnes[74] = 4 , DigitTens[74] = 7
buf [--charPos] = DigitOnes[r];
buf [--charPos] = DigitTens[r];
}
// Fall thru to fast mode for smaller numbers
// assert(i <= 65536, i);
for (;;) {
//对10除法
q = (i * 52429) >>> (16+3);
//对10取余
r = i - ((q << 3) + (q << 1)); // r = i-(q*10) ...
//digits的前10位是[0,9],也能拿到个位
buf [--charPos] = digits [r];
i = q;
if (i == 0) break;
}
//如果是负数,最后填上负号
if (sign != 0) {
buf [--charPos] = sign;
}
}
}
但在jdk中,并不都是直接用 a/b 除法 和 a%c 取余来获取商和取余数的
-
对100除法:q = i / 100,直接除法
-
对100取余:r = i - ((q << 6) + (q << 5) + (q << 2)); 推导如下:
- r = i - (q * 100)
- r = i - (q * 64 + q * 32 + q * 4)
- r = i - ((q << 6) + (q << 5) + (q << 2))
-
对10取余:r = i - ((q << 3) + (q << 1)); 推导如下:
- r = i - (q * 10)
- r = i - (q * 8 + q * 2)
- r = i - ((q << 3 + q << 1)