作为一名Java开发,相信大家对于Integer都不会陌生,接下来就其分析下
开箱与装箱
开箱装箱主要针对于Java中出现的几种包装类,比如int
与之对应的Integer
。通俗一点的理解就是,Integer
可以与int
自动的相互转换,这个转换过程对于开发人员来说是透明的,JVM在底层帮我们进行了转化。
装箱:
举个栗子:
Integer i = 10; //Integer i = new Integer(10)
上面的代码实质生成的Integer
对象,怎么生成的呢?,在JVM底层,它帮我们调用了Integer.valueOf(int x)
,这就是所谓的装箱过程。
拆箱:
举个栗子:
int b = i; //int i = Integer.intValue(i)
上面的代码是将Integer i
转换为int i
,怎么生成的呢?,在JVM底层,它帮我们调用了Integer.intValue(int x)
,这就是所谓的拆箱过程过程。
自动装箱存在的缓存
相信备战面试的小伙伴,都会知道Integer
在一段区域中会进行缓存,这个区间默认是[-127,128]
之间。注意奥:缓存只能在自动装箱
或者是调用Integer.valueOf(int x)
才会有用。
举个?:
Integer a = new Integer(11)
Integer b = new Integer(11)
a == b ? // false
为什么为false
?,因为a
,b
是两个对象,两个对象用==
,当然不相等啊,这比较的是内存地址,hashcode。用equals
才是值(value)的比较。
Integer a = 11
Integer b = 11
a == b ? // true
这里为什么又是true
?,因为a
,b
实质是同一个对象,只是引用不同,两者都指向的同一内存地址。前面说到[-127,128]
区间,就是当调用Integer.valueOf(int x)
(ps:装箱实质就是调用这个方法)时,在这个区间调用都是从缓存中直接拿值,超过这个区间才会创造新的对象。
下面看下源码:
public static Integer valueOf(int i) {
// low = -128 high = 127
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
考虑下如下场景:
Integer c = Integer.valueOf(100);
Integer b = new Integer(100);
b == c ? // false
你怎么讲?第一次调用Integer.valueOf
,对象从何而来?是不是觉得很奇怪,low<100<high
,cache
里面的值应该从哪里来?
思路一:
当我们每次创建对象的时候,通过值判断如果在区间[low ,high ]
,我们就将其缓存到cache
中,方便下次调用。
很明显,这样我们第一次调用值从那哪里来?这是一个问题,我想JDK的开发者不会这个蠢吧,hah。
思路二:
JVM启动时,在装载类的时候就将其给初始化,依次给[low ,high ]
创建对应的对象。这样就解决了第一次调用的问题了。
看下源码:
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;
}
确实是这样处理的,大家有没有注意到一点high
写死,而是在静态代码块中才初始化的值,还可以赋值进行变动,这说明了一个问题我们的缓存块是可以调整的,所以说Integer
仅仅在区间[-127,128]
进行缓存是不准确的,-127
是固定边界,high
边界是可以调整的。
注意: -XX:AutoBoxCacheMax=<size>
可以调整这个high
值的大小。
举个?:
Integer b = 200;
Integer c = 200;
System.out.println(c == b);
Integer a = 400;
Integer d = 400;
System.out.println(a == d);
进制转换
在Integer
中支持多种的进制转换,比如说十进制转换成二进制
、八进制
、十六进制
。在Integer
的进制转换是设计得十分巧妙的,不是像我们所说的那种辗转相除法
。下面我们分析一下源码:
toUnsignedString0函数
private static String toUnsignedString0(int val, int shift) {
// assert shift > 0 && shift <=5 : "Illegal shift value";
int mag = Integer.SIZE - Integer.numberOfLeadingZeros(val);
int chars = Math.max(((mag + (shift - 1)) / shift), 1);
char[] buf = new char[chars];
formatUnsignedInt(val, shift, buf, 0, chars);
// Use special constructor which takes over "buf".
return new String(buf, true);
}
几乎所有的进制转换都离不开这个函数,这个函数通过一个shift
来区分二进制、八进制、十六机制
,分别由1,3,4
表示,你说为啥没有2
,当然也是有的只不过内部不提供,需要你自己去调用相关API来自己实现,实现起来也比较方便,直接调用API.
观察重点:numberOfLeadingZeros与formatUnsignedInt的作用
numberOfLeadingZeros
看函数意思,大概就是寻找0
吧,
贴下源码:
public static int numberOfLeadingZeros(int i) {
// HD, Figure 5-6
if (i == 0)
return 32;
int n = 1;
if (i >>> 16 == 0) { n += 16; i <<= 16; }
if (i >>> 24 == 0) { n += 8; i <<= 8; }
if (i >>> 28 == 0) { n += 4; i <<= 4; }
if (i >>> 30 == 0) { n += 2; i <<= 2; }
n -= i >>> 31;
return n;
}
这段代码设计的十分巧妙,起初看的时候一脸懵逼,后来慢慢才发现一些蛛丝马迹。这是一个二分搜索:
用来寻找i
最高非0
位左边存在的0的个数。
大概逻辑就是:
- 先对
i
进行无符号右移16位判断(移走低16位,剩下高16位),如果这个i
全部包含在低16位中,那么我们需要加上16
。然后才是真正的移位。如果不包含,那么我们直接考虑高16位。 - 第一步舍去一半了,剩下的我们继续进行二分,与步骤1一样。
- 一直重复到还剩下两位,二分情况就是:
(16,8,4,2)
n -= i >>> 31;
通过对最后两位判断,如果首位为1那么我们需要减去初始的n=1
,如果首位为0那么我们什么都不错,这个0
在初始的时候就已经加上了。
chars 代表的是char的个数,mag代表的是除去前面0的个数
int chars = Math.max(((mag + (shift - 1)) / shift), 1);
为什么是(mag + (shift - 1)) / shift
,这是防止chars为0,我们需要chars至少为1,当mag=1时,((mag + (shift - 1)) / shift)
是为1的。我们使用shift - 1
formatUnsignedInt解析
static int formatUnsignedInt(int val, int shift, char[] buf, int offset, int len) {
int charPos = len;
int radix = 1 << shift;
int mask = radix - 1;
do {
buf[offset + --charPos] = Integer.digits[val & mask];
val >>>= shift;
} while (val != 0 && charPos > 0);
return charPos;
}
重点在于mask
,取移位后的反码,当shift
为4时,则mask=1111
,我们通过val & mask
4位4位的截取,就是16进制,1100
这样为一组.
charPos
为什么从高往低处自减?
11111111
对应的就是FF
,截取的顺序是从右到左,所以我们需要从高到低位的设置char[]
的值