近段时间由于没有什么事,就看起了源码,以前只知道,java 中的 Integer String 等类为了优化系统,采用了一系列的高速缓存机制,来提升系统的性能,但由于是在是太忙(太懒)故没有好好的研究过这方面的内容,这段时间终于没有什么杂事了,可以好好静下心来好好研究研究。
首先一个小例子
@Test
public void testIntegerCache(){
Integer a = 126, b = 126, c = 128, d = 128;
System.out.println(a == b);
System.out.println(c == d);
输出的结果是: 对于这个结果知道的可能就一笑置之了,但没接触过的可能会有那么的一点疑惑。(带着这个疑惑读下去)
我们都知道,在java 的 jdk 5.0 也就是 1.5 的版本中,加入了很多的新特性,自动装箱和自动拆箱就是其中的极其方便的一个新特性,何为自装箱和拆箱呢,
在jdk 5.0 之前:
//在jdk 5.0之前 包装类型和基本类型的相互转换的方法
Integer aNum = new Integer(10);
//包装类通过 xxxValue() 方法得到基本数据类型
int bNum = aNum.intValue();
//基本类型通过 valueOf(); 得到包装类型
aNum = Integer.valueOf(bNum);
而在jdk 5.0之后就可以这 干了:
//在jdk 5.0 之后 由于引入了 自动装箱和自动拆箱的特性 那么就可以这么写
Integer cNum = 10;
int dNum = cNum;
难道真的是一个对象可以赋值给一个基本数据类型,一个基本数据类型可以赋值给对象了吗? 这是不是不符合面向对象的原则了??通过 使用 javap 命令,进行反编译可以看到
从中可以看到,其实java 只是在语法上给予了开发人员的方便性,其底层还是调用了相应的方法进行对象及基本数据类型的转换。那么我们就可以去解决我们在刚开始的疑问了 。 在最开始 我们使用了 Integer a= 126,b=126,c = 128,d=128; 在此段程序中 由于是把基本数据类型赋值给了包装类型,所以此处会触发自动装箱操作。 即调用 相应的 valueOf() 方法进行包装 在此处调用的就是 Integer.valueOf(a); 操作,既然知道是调用了哪个方法,那么就可以去看 源码 在Integer 类中, 打开jdk源码 可以发现在 valueOf ( ) 方法中 就干了什么
public static Integer valueOf(String s) throws NumberFormatException {
return Integer.valueOf(parseInt(s, 10));
}
//这是一个重载的方法
/**当传入的参数 i 的值在 IntegerCache.low 和 IntegerCache.high 之间的时候,就直接返回这个数组中的对象 IntegerCache.cache[i+(-IntegerCache.low)]; 由于 数组中的下标都是 大于等于 0 的数字,所以就 需要加上 -IntegerCache.low 其实就是加上了最小负数的绝对值 128
*/
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
//发现了一个 IntegerCache 这是个什么东东 继续往下查看,原来 就是它完成了对数据的缓存操作,重要的部分我加上了注释,相信大家都能看懂了,看到这里,上面的那个疑问估计大家都已经解开了。
发现了这么一个 私有的静态内部类
private static class IntegerCache {
//定义 高速缓存的最小 值
static final int low = -128;
/** 定义高速缓存的最大 值
未直接设置,可以使用 -D java.lang.Integer.IntegerCache.high=xxx 进行动态设置*/
static final int high;
//定义缓存数组
static final Integer cache[];
//静态块,加载类的时候就执行,加载类的同时就进行初始化缓存数组
static {
// 默认的 缓存最大值
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
//如果自己设置了缓存的最大值,大于 127 那么久使用 设置的,否则就默认为 127
i = Math.max(i, 127);
// 设置的最大值 不能超过 Integer 所能表示的最大值
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// 如果类型不能转换那么就抛出异常
}
}
high = h;
//初始化缓存数组的长度 0 也算 所以加上 1
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
//循环进行缓存值得设置
cache[k] = new Integer(j++);
// 断言
assert IntegerC ache.high >= 127;
}
private IntegerCache() {}
}
经过上面的讨论,相信大家对java的自动装箱和拆箱 以及 数据对象的缓存机制已经有了一个清晰的认识, 对于其他的 数据类型也可以进行类比学习,但又有所不同,例如 Double Float 等类型,由于这些属于浮点型数据,在一个固定的范围内,数据的格式是无穷的,所以无法去做缓存,所以 每一个 触发了 double Short 数据的操作其实返回的都只是 一个新的对象 查看其源码发现也正是如此
public static Double valueOf(String s) throws NumberFormatException {
return new Double(parseDouble(s));
}
boolean 类型有点特殊,因为它只有 两个常量仅供选择,所以java就直接把其作为了 两个 静态最终常量
public static final Boolean TRUE = new Boolean(true);
/**
* The {@code Boolean} object corresponding to the primitive
* value {@code false}.
*/
public static final Boolean FALSE = new Boolean(false);
而valueOf() 也就是直接返回这两个 对象了,所以也就是同一个对象
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
需要注意的地方:
- 引发自动装箱和拆箱的条件
只有在基本数据类型和包装类之间进行直接的赋值的时候才会触发这个操作。 对于使用 new 运算符生成的对象 每次都是一个新的对象生成。故不会存在使用缓存对象的机会。 也就是 使用new 运算符生成的两个 数值相等的对象 但不是同一个对象 如下
@Test
public void testAutoBoxing(){
Integer a = new Integer(2);
Integer b = new Integer(2);
System.out.println(a == b);
}
结果输出的是 false
也就是说 使用new 运算符生成的对象是两个不同的对象,它并没有使用缓存数组中的缓存对象,而是自己又重新生成了一个对象,大家平时在开发的时候可以注意此处,进行优化处理。
2. 当 == 两边存在表达式而不全为常量的时候有会触发什么操作 如下:
@Test
public void testAutoBoxing(){
Integer aNum = 2;
Integer bNum = 4;
Integer cNum = 6;
Integer dNum = 6;
Integer eNum = 128;
Integer fNum = 128;
Double gNum = 6d;
Double hNum = 4d;
System.out.println(cNum == dNum);
//超出缓存最大值,生成新对象
System.out.println(eNum == fNum);
// 当 == 两边出现了表达式的时候,就会触发拆箱操作此时两端比较得就是 值的大小
System.out.println(cNum == (aNum+bNum));
System.out.println(cNum.equals(aNum+bNum));
System.out.println(gNum == (aNum+bNum));
// equals() 方法不会改变数据的类型,不同类型的对象相比,肯定是不同的
System.out.println(gNum.equals(aNum+bNum));
// 加法中类型的自动提升 equals() 当同类型的时候比较得就是 值的大小
System.out.println(gNum.equals(aNum+hNum));
}
输出结果为:
对应的原因我已经写在了注释中。
String 对象的装箱和拆箱操作和上面的不同之处较多,使用的机制并不是同一种,String 使用了字符串常量池的机制,今天就到这里,下次再续。