首页 > 编程 > Java > 正文

String VS StringBuilder VS StringBuffer In Java

2019-11-08 03:00:40
字体:
来源:转载
供稿:网友

简单说说 String

String 是不可变的,一旦定义了,就不能再去修改字符串的内容。

先看下面两行代码:

String a = "Hello";a = a + " world"

通常情况下很容易误解为修改了字符串对象的内容。其实不然,真实的操作则是:

“Hello” 是一个字符串对象,被赋给了引用 a;” world” 也是一个字符串对象,和 “Hello” 拼接生成一个新的字符串对象又被赋给了 a;

并不是 “Hello” 改变了,而是指向 “Hello” 的引用 a重新指向了新对象。

StringBuilder

StringBuilder 在很大程度上类似 ArrayList:

这里写图片描述

很明显如果需要连续拼接很多字符串的话 StringBuilder 比 String 更加方便。而且在性能方面也有考究,这点我们稍后再说。

StringBuffer

StringBuffer 基本上和 StringBuilder 完全一样了。明显的不同就是 StringBuffer 是线程安全的,除了构造方法之外的所有方法都用了 synchronized 修饰。

相对来说安全一些,但是性能上要比 StringBuilder 差一些了。

字符串拼接探索

先看一段代码:

public class Test { public static void main(String[] args) { String aa = "33"; aa = aa + 3 + 'x' + true + "2"; aa = aa + 8; String bb = aa + "tt"; System.out.PRintln(bb); }}

使用编译工具编译

javac Test.java

同级目录会生成 Test.class,我们再对 Test.class 进行反汇编

javap Test.class

得到下面的代码:

public class Test { public Test(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: ldc #2 // String 33 2: astore_1 3: new #3 // class java/lang/StringBuilder 6: dup 7: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V 10: aload_1 11: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 14: iconst_3 15: invokevirtual #6 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 18: bipush 120 20: invokevirtual #7 // Method java/lang/StringBuilder.append:(C)Ljava/lang/StringBuilder; 23: iconst_1 24: invokevirtual #8 // Method java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder; 27: ldc #9 // String 2 29: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 32: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 35: astore_1 36: new #3 // class java/lang/StringBuilder 39: dup 40: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V 43: aload_1 44: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 47: bipush 8 49: invokevirtual #6 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 52: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 55: astore_1 56: new #3 // class java/lang/StringBuilder 59: dup 60: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V 63: aload_1 64: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 67: ldc #11 // String tt 69: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 72: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 75: astore_2 76: getstatic #12 // Field java/lang/System.out:Ljava/io/PrintStream; 79: aload_2 80: invokevirtual #13 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 83: return}在第一次拼接的时候(连续加号),先创建了一个 StringBuilder 对象,然后 append 当前的字符串对象 aa,接着连续 append 将要拼接的元素,最后 toString() 返回拼接后的字符串对象赋给 aa;第二次拼接,同样是创建一个 StringBuilder 对象,然后 append 当前的字符串对象 aa,接着 append 8,最后 toString() 返回拼接后的字符串对象赋给 aa;第三次拼接,同样是创建一个 StringBuilder 对象,然后 append 当前的字符串对象 aa,接着 append “tt”,最后 toString() 返回拼接后的字符串对象赋给 bb;

我们把每出现一次 “=” 算成一次拼接,那么每次拼接都会创建一个 StringBuilder 对象。

当遇到大规模的场景中,比如循环次数很多,就像下面的例子:

public class Test1 { public static void main(String[] args) { Test1 test = new Test1(); System.out.println(test.testString()); System.out.println(test.testStringBuilder()); } public long testString(){ String a = ""; long start = Calendar.getInstance().getTimeInMillis(); for(int i=0;i<100000;i++){ a += i; } long end = Calendar.getInstance().getTimeInMillis(); return end-start; } public long testStringBuilder(){ StringBuilder a = new StringBuilder(); long start = Calendar.getInstance().getTimeInMillis(); for(int i=0;i<100000;i++){ a.append(i); } long end = Calendar.getInstance().getTimeInMillis(); return end-start; }}

输出:

22243 16

耗时比较,前者呈指数级增长,而后者是线性增长。性能上相差甚远。

甚至如果我们已经知道了容量,还可以继续优化,一次性分配一个 StringBuilder,避免扩容时候的开销。参考下面例子。

public class Test2 { public static void main(String[] args) { Test2 test = new Test2(); for(int i=0;i<5;i++){ System.out.println(test.testStringBuilder() + "---" + test.testStringBuilder2()); } } public long testStringBuilder(){ StringBuilder a = new StringBuilder(); long start = Calendar.getInstance().getTimeInMillis(); for(int i=0;i<10000000;i++){ a.append(1); } long end = Calendar.getInstance().getTimeInMillis(); return end-start; } public long testStringBuilder2(){ StringBuilder a = new StringBuilder(10000000); long start = Calendar.getInstance().getTimeInMillis(); for(int i=0;i<10000000;i++){ a.append(1); } long end = Calendar.getInstance().getTimeInMillis(); return end-start; }}

输出:

78—16 62—31 47—15 63—31 47—31

提前分配,耗时更短~


原则很简单:不要使用字符串连接操作符来合并多个字符串,除非性能无关紧要。应该使用 StringBuilder 的 append 方法。


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表