参考:
https://www.programmergate.com/stringbuilder-vs-stringbuffer/
https://blog.csdn.net/rmn190/article/details/1492013
https://droidyue.com/blog/2014/12/21/string-literal-pool-in-java/
String 字符串常量 StringBuffer 字符串变量(线程安全)StringBuilder 字符串变量(非线程安全)
String
Strings are constant; their values cannot be changed after they are created.
这是JavaAPI中对于String的定义,就是说String是常量,一旦定义就无法改变。说到常量就不得不提到Java中的字符串常量池,通过String的俩种创建方法来进行了解
String俩种创建方式区别
Java中字符串对象创建有两种形式,一种为字面量形式,如String str = “abc”;,另一种就是使用new这种标准的构造对象的方法,如String str = new String(“abc”);
当Java中出现第一种字面量形式创建字符串时,JVM首先会在常量池中进行查找是否存在相同字符串对象内容的引用,如果存在,那么直接将新创建的对象指向该引用地址,如果不存在则创建新的引用对象,并把该引用对象放入常量池
public class Test {
public static void main(String[] args) {
String str1 = "abc";
String str2 = "abc";
System.out.println(str1 == str2); // true
}
}
通过以上代码可以看到结果为true
也就是说str1
str2
指向的是相同的地址。现在将代码改变:
public class Test {
public static void main(String[] args) {
String str1 = "abc";
str1 = "xyz";
String str2 = "abc";
System.out.println(str1 == str2); // false
}
}
当JVM看到"xyz"
,在字符串常量池创建新的String对象存储它,再把新建的String对象的引用返回给str1
,所以这时候str1
指向的是xyz
,而abc
还是存在于字符串常量池中,其值并不能被改变,所以返回false
接下来继续将代码改为以下:
public class Test {
public static void main(String[] args) {
String str1 = "abc";
str1 = "xyz";
String str2 = "abc";
System.out.println(str1 == str2); // false
String str3 = new String("abc");
System.out.println(str2 == str3); // false
}
}
通过new
来创建一个新的String对象str3
,这个过程是首先,JVM在常量池中找是否存在abc
,如果找到,不做任何事情,如果没找到,在常量池中创建该对象;接着由于有new
关键字,会在栈内存空间(不是字符串常量池)中创建str3
对象,并指向堆内存空间(不是字符串常量池)中的abc
,接下来进行str2 == str3
比较的时候肯定不是引用的同一个对象,所以返回false
所以说使用new创建String对象过程中是产生了俩个对象
另外Java中还提供了如何将new创建的对象手动放入常量池,看以下代码:
public class Test {
public static void main(String[] args) {
String str1 = "abc";
str1 = "xyz";
String str2 = "abc";
System.out.println(str1 == str2); // false
String str3 = new String("abc");
System.out.println(str2 == str3); // false
String str4 = str3.intern();
System.out.println(str2 == str4); // true
}
}
这样结果就返回的是true
StringBuilder VS StringBuffer
通过上面的介绍我们简单的了解了String是不可变的,是一个常量,因此当我们使用String来构建一个动态的字符串对于内存的开销是特别大的,因为对于每个字符串我们都得分配新的内存空间
所以在Java开发初就提供了StringBuffer
JavaAPI对于StringBuffer
的说明是
A thread-safe, mutable sequence of characters.
这个可变的类来创建动态的字符数组,而这个只需要分配一次内存,这与String相比是节省了大量的内存空间,另外,由于其是线程安全的,所以其所有的public
方法都是synchronized
,而这样的设计就会导致,当开发人员是在单线程的环境中时,就会增加额外的开销,而且大多数对于StringBuffer
的使用基本上都是在单线程的环境中,所以开发人员根本不需要对于synchronized
的额外开销,所以在jdk1.5之后提供一个新的类StringBuilder
,这个类不是线程安全的也不是同步的,但是其使用更加方便而且在单线程环境中更加快速
性能测试
下面通过代码进行测试当创建动态的字符数组时String、StringBuffer、StringBuilder
三者需要的时间对比,测试代码如下:
public class Test {
public static void main(String[] args) {
new Test().constructDynamicString();
}
public void constructDynamicString()
{
long startTime = System.currentTimeMillis();
String s = "test";
//StringBuffer s = new StringBuffer("test");
//StringBuilder s = new StringBuilder("test");
for(int i=0; i<100000; i++)
{
s += "concat";
//s.append("concat");
}
long endTime = System.currentTimeMillis();
System.out.println("Concat time ====== " + (endTime - startTime) + "ms");
}
}
结果如下:
String创建时间: 43144ms
StringBuffer创建时间: 14ms
StringBuilder创建时间: 14ms
第一次测试结果都是14ms,应该是数字较小,循环1000000次,统计结果
StringBuffer: Concat time ====== 80ms
StringBuilder: Concat time ====== 43ms
通过以上测试可以看出,在创建动态字符串过程中,使用String需要的时间最久,而使用StringBuilder时间最短,而且创建的次数越多这个性能越明显。
总结
通过以上的分析,那么在实际使用中我们怎么选择呢?
1、如果创建静态的字符串而且在程序中是不变的,那么选择String
2、如果是创建动态字符串,并且是在多线程中使用,那么用StringBuffer
3、其他的都使用StringBuilder,并且大多数情况下都是使用StringBuilder