public static void main(String[] args) {
String a = "1" + "2" + "3";
String b = "123";
String c = new String("123");
System.out.println(a == b);
System.out.println(a == c);
}
答案:true
false
如果你答对了,你知道为什么吗?
首先 “==” 对于基本数据类型来说是直接比较它们的值。那么如果是引用类型呢?其实比较的是两个对象的引用地址值是否相同。
提到String不得不说下equals()方法,是在Object中定义的。它默认定义的就是使用“==”来比较(下面是源码)
public boolean equals(Object obj) {
return (this == obj);
}
在String中是重写了这个方法,用于比较值是否相等。在看下String a = "1" + "2" + "3" 和 String b = "123"; a是通过“+”赋值,而b是通过直接赋值。
由于JVM在编译时会做优化,所以 String a = "1" + "2" + "3"; 在编译时即为String a = “123”;
因为JVM会认为"1、2、3"这3个常量叠加后是一个固定的值,不同在运行时运算,所以采取优化并且存放在常量池中。
你真的明白了吗?看看下面的例子
public static void main(String[] args) {
String a = "1";
String b = "2";
final String c = "12";
String d = a + b;
String e = "12";
System.out.println(c == d);
System.out.println(e == d);
System.out.println(c == e);
}
答案:false
false
true
怎么样,这次答对了吗?
之前说过JVM在编译期间会做优化,当运行String d = a + b;时,JVM也不能确定会发生什么(有很多情况会使它在运行时方法改变如:字节码增强技
术)。所以它就无法做出优化,以在编译时是如下结果
StringBuilder temp = new StringBuilder();
temp.append(a).append(b);
String b = temp.toString();
由于变量c被final修饰,那么JVM自然会认为它是不可变的。
补充两个问题
1、String.intern();
这个方法大家可能平时很少用到,当调用这个方法的时候,JVM会从常量池中去循环做匹配,如果找到了就直接
返回这个对象的地址,如果没找到则会创建一个并返回创建后对象的地址。
String a = "12";
String b = a.intern();
System.out.println(a == b);
答案:
true
String a = "1" + "2";大家可能经常使用这种方式对字符串进行拼接。 之前也说了,因为都是常量,
所以JVM会在编译期间优化。
如果是动态拼接呢?下面这个方式相信大家也肯定经常使用。
String a = null;
for(int i=0; i<n; n++){
a = a + i;
}
其实每循环一次,内部都在做
StringBuilder temp = new StringBuilder();
temp.append(a).append(i);
a = temp.toString();
如果循环次数较多,会非常消耗内存,严重会导致内存溢出。看下StringBuilder中的部分源码
int newCapacity = value.length * 2 + 2;
if (newCapacity - minimumCapacity < 0)
newCapacity = minimumCapacity;
if (newCapacity < 0) {
if (minimumCapacity < 0) // overflow
throw new OutOfMemoryError();
newCapacity = Integer.MAX_VALUE;
}
value = Arrays.copyOf(value, newCapacity);
}
从源码中可以看出,如果每次空间不足时,会按照最少二倍的方式扩容(默认是16个字节),对象的数据会暂时保留,知道扩容完成。那么就相当于需要三倍的内存空间,这样循环下去,内存的消耗可想而知。
简单的说就是在运行时频繁的做“+”拼接,会使堆中Young空间不足导致频繁的GC(复制算法),从而进入Old区域,
然而Old区域与Young区域不同(后面说JVM内存模型及GC策略时会详细说明),会发生FULL GC,时间会非常的慢。
总结:需要大量的动态“+”拼接时,用StringBuilder而不要使用“+”。