JVM对字符串的处理
JVM为了节省内存,引入了常量池的概念,用来存储可共享的字符串常量,它属于方法区的一部分。
String str1 = new String("字符串");
上面的字符串对象创建语句其实是创建了两个字符串对象,其中一个是"字符串"直接量对应的字符串对象。另外一个是有new String()构造器创建的字符串对象。
对于Java程序中的字符串常量,JVM会使用一个字符串常量池来保存它们:当第一次使用某一个字符串直接量时,JVM会将它放入字符串池进行缓存。在一般情况下,字符串池中的字符串对象不会被垃圾回收,当程序再次需要使用该字符串时,无需重新创建一个新的字符串,而是直接让引用变量指向字符串池中已有的字符串。例如:
public class Test {
public static void main(String[] args) {
String str1 = new String("字符串");
String str2 = "字符串";
String str3 = "字符串";
if(str1 == str2) {
System.out.println("str1与str2指向同一个对象");
}else {
System.out.println("str1与str2指向不同的对象");
}
if(str2 == str3) {
System.out.println("str2与str3指向同一个对象");
}else {
System.out.println("str2与str3指向不同的对象");
}
}
}
运行结果:
从结果可以看到引用str1指向new String()构造器创建的字符串对象,和引用str2指向不同的的字符串对象;而str2和str3都是使用的字符串常量进行创建的,它们都指向字符串池中的"字符串"字符串。
其实,如果一个字符串连接表达式的值可以在编译时确定下来,那么JVM会在编译时计算该字符串变量的值,并让它指向字符串池中对应的字符串。
例如:
public class Test {
public static void main(String[] args) {
String str1 = "String字符串";
String str2 = "String" + "字符串";
if(str1 == str2) {
System.out.println("str1与str2指向同一个对象");
}else {
System.out.println("str1与str2指向不同的对象");
}
}
}
运行结果:
上面的str2的值是一个字符串拼接表达式,但是这个字符串连接表达式的值在编译时就能确定下来,因此JVM将在编译时计算str2的值,并让str2指向字符串池中对应的字符串。
那么怎么样的表达式可以在编译时就确定值呢?当表达式中都是直接量(字符串直接量或数值直接量),没有变量参与,没有方法调用时,这个表达式的值就能在编译时确定下来。
所以,当程序中需要使用字符串、基本类型包装类实例时,应该尽量使用字符串直接量、基本类型直接量,避免通过new String()、new Integer()的形式来创建字符串和基本类型包装类的实例,这样能保证较好的性能。
StringBuffer和SringBuilder
StringBuffer和StringBuilder也可以创建字符串,只不过String类是不可变类,创建的是字符串常量,而StringBuffer和StringBuilder是可变的,创建的是字符串变量。
public class Test {
public static void main(String[] args) {
String str1 = "字符串";
StringBuffer str3 = new StringBuffer("StringBuffer");
System.out.println(str3.hashCode());
str3 = str3.append(str1);
System.out.println(str3.hashCode());
}
}
运行结果:
可以看到,str3对象在拼接了str1后并没有指向新的对象。
但是要注意,StringBuffer和StringBuilder不能直接使用字符串直接量进行创建,需要使用new关键字,同时也不能使用运算“+”进行字符串的拼接,当需要拼接字符串的时候,需要调用相应的方法。
同时,StringBuffer是线程安全的,StringBuilder是线程不安全的。也就是说StringBuffer类中绝大部分方法都增加了synchronized修饰符,对方法增加synchronized修饰符可以保证该方法线程安全,但是会降低执行效率,所以StringBuilder的性能强于StringBuffer。
总的来说: 少量字符串操作用String; 单线程下操作大量字符串用StringBuilder; 多线程下操作大量字符串用StringBuffer。