深入源码分析Integer类

深入源码分析Integer类

Java中的包装类与基本类型一一对应,为了让你拥有面向对象的特点,方便来同一管理,所以就创造了基本类型的包装类,属于引用类型,并且可以实现自动拆装箱,实现基本类型和包装类的自动转换,其中Integer类的源码很值得深入分析和思考学习。

Integer类的关键源码

  • 包装类都继承了Number 抽象类,并且Number抽象类实现了序列化接口,说明是可序列化的
  • 实现了接口Comparable,即实现了compareTo方法
  • 重写了hashCode和equals方法,其中hashCode是value,而equals只是比较同种类型的intValue方法返回的值,intValue的返回值也是value
  • Short、Byte、Long、Integer包装类中都含有一个对应的内部缓存类,区间都在[-128,127],缓存上界限,可以通过JVM属性来配置,默认为127,用于建立在此区间的数值的缓存,初始化数组将一定范围的整数放到cache数组中,然后在调valueOf方法的时候首先判断范围然后从缓存数组中去抓取数据 ,提高效率
  • valueOf()这个方法去创建Integer对象,Integer a = 100;这样的也是走的这个方法,这样就会先从缓存中获取对象;以new的方式创建对象,每个对象的内存值都不一样,在堆中重新分配内存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public final class Integer extends Number implements Comparable<Integer> {

public static final Class<Integer> TYPE = (Class<Integer>) Class.getPrimitiveClass("int");//注意此静态属性,意思是调用虚拟机的本地方法,创建原始类型int的class实例

//做了一个字符数组字典,可用于缓存,直接列出所有可能的字符。final修饰符修饰的变量在由于其本身的特性,在编译期就能直接确定其值,且此值不可变。在编译过程中,可以直接将其变量直接转换成其值本身去表示。
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'
};
//主要属性value初始化值
private final int value;
//构造器
public Integer(int value) {
this.value = value;
}
public Integer(String s) throws NumberFormatException {

this.value = parseInt(s, 10);
}

hashCode、equals和compareTo方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public int compareTo(Integer anotherInteger) {
return compare(this.value, anotherInteger.value);
}

public static int compare(int x, int y) {
return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
//重写了hashCode方法,直接返回的是属性值value,即初始化值
public int hashCode() {
return value;
}

public boolean equals(Object obj) {
//只有同种类型的才能进行判断是否相等
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
//intValue方法也是直接返回的value值
public int intValue() {
return value;
}

parseInt方法

1
2
3
4
//没有指定进制的,默认为10进制的字符串,经常使用的静态方法
public static int parseInt(String s) throws NumberFormatException {
return parseInt(s,10);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
  
//radix是字符串s对应的进制
public static int parseInt(String s, int radix)
throws NumberFormatException
{
/*
* WARNING: This method may be invoked early during VM initialization
* before IntegerCache is initialized. Care must be taken to not use
* the valueOf method.
*/

if (s == null) {
throw new NumberFormatException("null");
}

if (radix < Character.MIN_RADIX) {
throw new NumberFormatException("radix " + radix +
" less than Character.MIN_RADIX");
}

if (radix > Character.MAX_RADIX) {
throw new NumberFormatException("radix " + radix +
" greater than Character.MAX_RADIX");
}

int result = 0;
boolean negative = false;
int i = 0, len = s.length();//i表示当前遍历的s的位数
//此处做了严谨性分析,设置最小值为负的Integer的最大值,预防溢出
int limit = -Integer.MAX_VALUE;
int multmin;
int digit;

if (len > 0) {
//获取第一位字符
char firstChar = s.charAt(0);
if (firstChar < '0') { // Possible leading "+" or "-"
if (firstChar == '-') {
//判断是否为负数
negative = true;
//将限制转换为Integer的最小值,不能小于Integer的最小值
limit = Integer.MIN_VALUE;
} else if (firstChar != '+')//第一个char符号判断,不是正负号就直接抛异常
throw NumberFormatException.forInputString(s);

if (len == 1) // Cannot have lone "+" or "-" 若只有一个符号,则抛出异常
throw NumberFormatException.forInputString(s);
i++;
}
multmin = limit / radix;//设定不同进制下的极限值
while (i < len) {//进行进制的转换
// Accumulating negatively avoids surprises near MAX_VALUE
digit = Character.digit(s.charAt(i++),radix);//将数字字符串转换成要求的进制数,使用工具类,每次遍历对一个字符进行操作转换
if (digit < 0) {
throw NumberFormatException.forInputString(s);
}
if (result < multmin) {
throw NumberFormatException.forInputString(s);
}
result *= radix;
if (result < limit + digit) {
throw NumberFormatException.forInputString(s);
}
result -= digit;
}
} else {
throw NumberFormatException.forInputString(s);
}
return negative ? result : -result;//根据符号返回正数还是负数
}

IntegerCache内部缓存类

Short、Byte、Long、Integer包装类中都含有一个对应的内部缓存类,区间都在[-128,127],缓存上界限,可以通过JVM属性来配置,默认为127,用于建立在此区间的数值的缓存,初始化数组将一定范围的整数放到cache数组中,然后在调valueOf方法的时候首先判断范围然后从缓存数组中去抓取数据 ,提高效率。

推荐使用valueOf()这个方法去创建Integer对象,Integer a = 100;这样的也是走的这个方法,这样就会先从缓存中获取对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
//巧妙设计的缓存内部类
private static class IntegerCache {
//缓存的界限,不可变的,下界:-128
static final int low = -128;
//缓存上界,静态初始化为0
static final int high;
//还是利用数组来缓存
static final Integer cache[];

static {
// high value may be configured by property
// 缓存上界限,可以通过JVM属性来配置,默认为127
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
//根据JVM的配置是否读取到配置上界信息,并与127比较,最后取值为127到int类型数据最大值之间的数
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);// 最小值也就是127了,或者比127大
// Maximum array size is Integer.MAX_VALUE
// 不能比int类型最大值还大啊
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;
//缓存所有Integer的数据,存入数据
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() {}
}
//重点啊,节省开销,官方也推荐使用这个方法去创建对象的,Integer a = 100;这样的也是走的这个方法
public static Integer valueOf(int i) {
//如果传入的i值在IntegerCache缓存中,则直接取出
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
//否则,直接创建一个实例
return new Integer(i);
}

使用Integer创建对象时常见问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
      Integer a1 = 100;
Integer a2 = 100;
Integer b1 = 200;
Integer b2 = 200;

Integer c1 = Integer.valueOf(100);
Integer c2 = Integer.valueOf(100);

Integer c3 = new Integer(100);
Integer c4 = new Integer(100);

Integer d1 = Integer.valueOf(200);
Integer d2 = Integer.valueOf(200);
// 以new的方式创建对象,每个对象都不会内存值一样,在堆中重新分配的内存
Integer d3 = new Integer(200);
Integer d4 = new Integer(200);

System.out.println(a1 == a2);//true 因为Integer的缓存机制,缓存了[-128,127],可以直接取出;
System.out.println(b1 == b2);//false 超过了缓存的那个范围,就建了个新对象,内存值不一样,返回false
System.out.println(c1 == c2);//true
System.out.println(d1 == d2);//false
// 以new的方式创建对象,每个对象都不会内存值一样,在堆中重新分配的内存
System.out.println(c3 == c4);//false
System.out.println(d3 == d4);//false
}

因为Integer的缓存机制,缓存了[-128,127],这些可以直接取出;

超过了缓存的那个范围,就建了个新对象,内存值不一样,返回false

以new的方式创建对象,每个对象的内存值都不一样,在堆中重新分配内存

toString(int i, int radix)方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
	//该方法使用到的字典
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'
};
//用于返回第二个参数所指定的进制数的第一个参数的字符串表示形式,带有进制
public static String toString(int i, int radix) {
if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX)
radix = 10;//默认10进制
/* Use the faster version */
//快速高效,如果为十进制直接toString方法
if (radix == 10) {
return toString(i);
}
char buf[] = new char[33];
boolean negative = (i < 0); // 确认是否为负数
int charPos = 32;

//当不是负数,将其转为负数,统一转成负数处理,这里是为了防止数据溢出
//若不这么做,当其是负数时,将负数转变为正数,则会发生数据溢出,毕竟int的数据范围是[-2^31, 2^31-1],当Integer.MIN_VALUE=-2^31转化为正数时,绝对会溢出,防止,若是将Integer.MAX_VALUE=2^31-1转化为负数,就肯定没有数据溢出了。
if (!negative) {
i = -i;
}
//利用字典数组做进制转换
//取余之后,余数串倒转就是其对应的进制串
while (i <= -radix) {
buf[charPos--] = digits[-(i % radix)];
i = i / radix;
}
buf[charPos] = digits[-i];
if (negative) {
buf[--charPos] = '-';
}
//注意是新建了一个String对象
return new String(buf, charPos, (33 - charPos));
}

//上面最后返回的创建对象的方法,来自于java.lang.String.java
public String(char value[], int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
// Note: offset or count might be near -1>>>1.
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
this.value = Arrays.copyOfRange(value, offset, offset+count);
}

//上面涉及到的Arrays工具类的方法,在之前也提到过
public static char[] copyOfRange(char[] original, int from, int to) {
int newLength = to - from;
if (newLength < 0)
throw new IllegalArgumentException(from + " > " + to);
//根据长度新建一个字符数组,之后使用System.arraycopy进行数组拷贝
char[] copy = new char[newLength];
System.arraycopy(original, from, copy, 0,
Math.min(original.length - from, newLength));
return copy;
}

toString(int i)方法

把int型包装成Integer然后再转化成String字符串

其中的细节难理解点:

  • q = (i * 52429) >>> (16+3); // 实质是和i/10效果一致,但是为什么要这样写呢?

    因为2<<(16+3)=2<<19=524288,

    (i 52429)>>>(16+3) = i \52429/524288=52429.0/524288=0.1000003814697….6位的精度已经足够多了,所以就是i*0.1=i/10,此处这样设计是为了提高精度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
     public static String toString(int i) {
//当是最小值时,不适合使用以下方法,因为会发生数据溢出(在调用stringSize时),故直接返回
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);
//根据字符数组创建String对象
return new String(buf, true);
}

//toString方法中填充字符数组的主要方法
static void getChars(int i, int index, char[] buf) {
int q, r;
int charPos = index;
char sign = 0;

if (i < 0) { //如果i为负数,则符号字符为'-'
sign = '-'; //设置为负号
i = -i; //将负数转化为正数统一处理
}

// Generate two digits per iteration
// 主要意思是,每次循环获取i中的最后两位,即十位和个位上的数字对应的字符,并将其保存到字符数组中
// 如果i大于65536,将值判断大小后取每个数字,较大的数字一次取两位(大数字运算消耗大)
while (i >= 65536) {
q = i / 100;
// really: r = i - (q * 100);
// 利用位运算,每次获得i的最后两位数,不断循环提取处理
r = i - ((q << 6) + (q << 5) + (q << 2));
i = q;
//获取其对10的余数,即 r%10,即存储r中在个位数集合中对应的字符
buf [--charPos] = DigitOnes[r];
//获取其对10的商,即 r/10 ,即是存储r中在十位数集合中对应的字符
buf [--charPos] = DigitTens[r];
}

// Fall thru to fast mode for smaller numbers
// assert(i <= 65536, i);
// i<=65536的情况,小数字运算消耗较小,故一次只取一位
for (;;) {
// 实质是和i/10效果一致,但是为什么会这样写呢?
//因为2<<(16+3)=2<<19=524288,
//(i * 52429)>>>(16+3) = i*52429/524288=52429.0/524288=0.1000003814697......
//6位的精度已经足够多了,所以就是i*0.1=i/10,此处这样设计是为了提高精度
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; //设置符号
}
}

//下面两个是用来确定字符串长度的。
//定义sizeTable表示int中每个位数中最大的数,用于简便确定int数的长度。
final static int [] sizeTable = { 9, 99, 999, 9999, 99999, 999999, 9999999,
99999999, 999999999, Integer.MAX_VALUE };
//使用上面的sizeTable定义来确定int数的字符串表示长度。
static int stringSize(int x) {
for (int i=0; ; i++)
if (x <= sizeTable[i])
return i+1;
}
static int stringSize(int x) {
//基于范围的查找
for (int i=0; ; i++)
if (x <= sizeTable[i])
return i+1;
}


//上面方法涉及到的字典,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',
} ;