当前位置 博文首页 > 外星喵的博客:面试常问关于Stirng、StringBuffer和StringBuilde
基本对比:
测试源码:
public static void performanceTest(int frequency) {
String str = "";
StringBuilder stringBuilder = new StringBuilder();
StringBuffer stringBuffer = new StringBuffer();
long totalTime = 0;//记录每个对象记录测试总耗时
long time; //记录每个对象记录一个周期测试耗时
int i = 0;
int cycle = 10; //测试周期
for (int j = 0; j < cycle; j++) {
//时间单位为纳秒
time = System.nanoTime();
while (i++ < frequency) {
str += "A";
}
totalTime += System.nanoTime() - time;
str = "";
}
str = null;
System.gc();//进行一次垃圾回收,避免缓存对后续测试的影响。
System.out.println("str 累加" + frequency + "个长度为1的字符串,平均耗时为:" + totalTime / cycle);
totalTime = i = 0;
for (int j = 0; j < cycle; j++) {
time = System.nanoTime();
while (i++ < frequency) {
stringBuffer.append("A");
}
totalTime += System.nanoTime() - time;
stringBuffer = new StringBuffer();
}
stringBuffer = null;
System.gc();
System.out.println("stringBuffer 累加" + frequency + "个长度为1的字符串,平均耗时为:" + totalTime / cycle);
totalTime = i = 0;
for (int j = 0; j < cycle; j++) {
time = System.nanoTime();
while (i++ < frequency) {
stringBuilder.append("A");
}
totalTime += System.nanoTime() - time;
stringBuilder = new StringBuilder();
}
stringBuilder = null;
System.gc();
System.out.println("stringBuilder累加" + frequency + "个长度为1的字符串,平均耗时为:" + totalTime / cycle);
System.out.println();
}
public static void main(String[] args) {
performanceTest(100);
performanceTest(10000);
performanceTest(1000000);
}
测试结果(时间单位为纳秒,100000纳秒 = 1毫秒):
在大量字符串拼接的场景中,如果对象被定义成String类型,会产生很多无用的中间对象,浪费内存空间,效率低。
这时,我们可以用更高效的可变字符序列:StringBuilder和StringBuffer,来定义对象。
那么,StringBuilder和StringBuffer有啥区别?
StringBuffer对各主要方法加了synchronized关键字,而StringBuilder没有。所以,StringBuffer是线程安全的,而StringBuilder不是。
其实,我们很少会出现需要在多线程下拼接字符串的场景,所以StringBuffer实际上用得非常少。一般情况下,拼接字符串时我们推荐使用StringBuilder,通过它的append方法追加字符串,它只会产生一个对象,而且没有加锁,效率较高。
String a = “123”;
String b = “456”;
StringBuilder c = new StringBuilder();
c.append(a).append(b);
System.out.println?;
接下来,关键问题来了:字符串拼接时使用String类型的对象,效率一定比StringBuilder类型的对象低?
答案是否定的。
为什么?
使用javap -c StringTest命令反编译:
从图中能看出定义了两个String类型的参数,又定义了一个StringBuilder类的参数,然后两次使用append方法追加字符串。
如果代码是这样的:
String a = “123”;
String b = “789”;
String c = a + b;
System.out.println?;
使用javap -c StringTest命令反编译的结果会怎样呢?
我们会惊讶的发现,同样定义了两个String类型的参数,又定义了一个StringBuilder类的参数,然后两次使用append方法追加字符串。跟上面的结果是一样的。
其实从jdk5开始,java就对String类型的字符串的+操作做了优化,该操作编译成字节码文件后会被优化为StringBuilder的append操作。
结论:
当拼接的字符串数量较小时,String、StringBuffer、StringBuild执行耗时可以忽略不记。
StringBuilder和StringBuffer类拥有的成员属性以及成员方法基本相同,区别是StringBuffer类的成员方法前面多了一个关键字:synchronized,所以StringBuffer比StringBuild多了一部分用于线程同步的开销。
随着拼接的字符串数量越来越大,String性能的下降尤为明显,按性能由小到大为:String < StringBuffer < StringBuilder。
使用建议:
String str=“hello world”
通过直接赋值的形式可能创建一个或者不创建对象,如果"hello world"在字符串池中不存在,会在java字符串池中创建一个String对象(“hello world”),常量池中的值不能有重复的,所以当你通过这种方式创建对象的时候,java虚拟机会自动的在常量池中搜索有没有这个值,如果有的话就直接利用他的值,如果没有,他会自动创建一个对象,所以,str指向这个内存地址,无论以后用这种方式创建多少个值为”hello world”的字符串对象,始终只有一个内存地址被分配。
String str=new String(“hello world”)
通过new 关键字至少会在JVM堆中创建一个对象,也有可能创建两个(取决于字符串常量池是否存在此字符串)。
因为用到new关键字,肯定会在堆中创建一个String对象,如果字符池中已经存在"hello world",则不会在字符串池中创建一个String对象,如果不存在,则会在字符串常量池中也创建一个对象。他是放到堆内存中的,这里面可以有重复的,所以每一次创建都会new一个新的对象,所以他们的地址不同。
相对于StringBuffer和StringBuilder,String 有一个intern() 方法,用来检测在String常量池是否已经有这个String存在。
cs