Fork me on GitHub

Integer源码解析

前言

今天我们来分析一下Integer源码。

Integer是八种包装类里面的比较常用的一种。那在使用时有什么注意及学习的地方呢?

让我们一起来看一下

分析

Integer是包java.lang下的一个类。

1
2
3
public final class Integer extends Number implements Comparable<Integer> {
//code
}

其被定义成final类型,继承Number类实现Comparable接口。

1
2
3
@Native public static final int   MIN_VALUE = 0x80000000;
@Native public static final int MAX_VALUE = 0x7fffffff;
public static final Class<Integer> TYPE = (Class<Integer>) Class.getPrimitiveClass("int");

可以看出,其定义了Integer的最大值为2^31-1,最小值为-2^31。Integer的基本数据类型为int。

我们来看一下Integer的toString方法,是比较有趣的。

1
2
3
4
5
6
7
8
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);
}

方法中使用到了stringSize函数,就是求这个Integer数的长度,我们来看看他是如何实现的。

1
2
3
4
5
6
7
8
9
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;
}

可以看到这段代码在计算Integer数长度时,构建了一个一维数组,然后拿x与数组每个值进行比较。而未使用我们经常说的除法或乘法计算长度。我们可以看下源码里的注释。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// I use the "invariant division by multiplication" trick to
// accelerate Integer.toString. In particular we want to
// avoid division by 10.
//
// The "trick" has roughly the same performance characteristics
// as the "classic" Integer.toString code on a non-JIT VM.
// The trick avoids .rem and .div calls but has a longer code
// path and is thus dominated by dispatch overhead. In the
// JIT case the dispatch overhead doesn't exist and the
// "trick" is considerably faster than the classic code.
//
// TODO-FIXME: convert (x * 52429) into the equiv shift-add
// sequence.
//
// RE: Division by Invariant Integers using Multiplication
// T Gralund, P Montgomery
// ACM PLDI 1994

我们知道计算机在计算除法效率要比加减乘法低。所以为了避免除法,提高计算效率,采用此种方法。

正好我们可以看看Long的toString方法里的stringSize方法。

1
2
3
4
5
6
7
8
9
10
// Requires positive x
static int stringSize(long x) {
long p = 10;
for (int i=1; i<19; i++) {
if (x < p)
return i;
p = 10*p;
}
return 19;
}

可以看到使用了乘法。你或许会问为什么没有像Integer那样构建一个数组去比较?额,如果要构造数组,那要构造一个19位的数组,里面有1-19位的数,代码写起来很多很臃肿吧,而且构造好的数组会长期放在内存中,我们知道,在实际应用中,Integer的使用频率要比Long高多了,长期让Long里面的一个数组占据内存空间也不太合理。以上是我个人见解。

我们再来看一下parseInt方法。

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
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();
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;
limit = Integer.MIN_VALUE;
} else if (firstChar != '+')
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;
}

我们可以看到此方法首先进行异常处理,然后判断传入String是否有正负号,然后截取位数,使用乘法,用减法得到int值,然后判断正负并返回结果。

我们再来看下Integer的内部类IntegerCache。

这儿可以说是一个坑,也是比较有意思的地方。

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
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() {}
}

很容易理解这段代码,初始化Integer后,IntegerCache会缓存[-128,127]之间的数据,这个区间的上限可以配置,取决于java.lang.Integer.IntegerCache.high这个属性,这个属性在VM参数里为-XX:AutoBoxCacheMax=2000进行设置调整或者VM里设置-Djava.lang.Integer.IntegerCache.high=2000。所以Integer在初始化完成后会缓存[-128,max]之间的数据。

并且我们可以看到valueOf方法。

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

可以看到valueOf方法,在cache范围内,返回的是缓存的值,是相同的对象,不在cache范围内,才会新建Integer。

由于有了Integer缓存,我们可以测试以下代码。

1
2
3
4
5
6
7
8
Integer a=1;
Integer b=1;
Integer c=new Integer(1);
Integer d=1000;
Integer e=1000;
System.out.println(a==b);
System.out.println(b==c);
System.out.println(d==e);

可以看到结果为true,false,false。

这样,我们在比较Integer时,如果仅仅比较值相等,建议使用equals方法比较。

我们可以看下Integer的equals方法。

1
2
3
4
5
6
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}

可以看到它会先判断类型是否符合,然后进行拆箱比较操作。

同样,在Long,Byte,Short,我们也可以看到缓存,其缓存数据长度均是-128到127。

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
//Long
private static class LongCache {
private LongCache(){}

static final Long cache[] = new Long[-(-128) + 127 + 1];

static {
for(int i = 0; i < cache.length; i++)
cache[i] = new Long(i - 128);
}
}
//Byte
private static class ByteCache {
private ByteCache(){}

static final Byte cache[] = new Byte[-(-128) + 127 + 1];

static {
for(int i = 0; i < cache.length; i++)
cache[i] = new Byte((byte)(i - 128));
}
}
//Short
private static class ShortCache {
private ShortCache(){}

static final Short cache[] = new Short[-(-128) + 127 + 1];

static {
for(int i = 0; i < cache.length; i++)
cache[i] = new Short((short)(i - 128));
}
}

关于缓存的意义:在该范围内数据比较常用,添加缓存提高性能。不用每次都新建,浪费系统资源。

同时根据Integer的hashCode方法,我们可以看到,Integer的hashCode返回本身的int值。

1
2
3
4
@Override
public int hashCode() {
return Integer.hashCode(value);
}

结论

以上就是Integer的源码分析,可以看到,对于偏底层的一些调用频繁的类,Java都做了很多方面的优化。包括从性能及内存开销等诸多方面。是值得我们学习和理解的。




-------------文章结束啦 ~\(≧▽≦)/~ 感谢您的阅读-------------

SakuraTears wechat
扫一扫关注我的公众号
您的支持就是我创作的动力!
0%