Welcome 微信登录
编程资源 图片资源库 蚂蚁家优选 PDF转换器

首页 / 操作系统 / Linux / String VS StringBuilder VS StringBuffer In Java

简单说说 String

String 是不可变的,一旦定义了,就不能再去修改字符串的内容。先看下面两行代码:String a = "Hello";a = a + " world"通常情况下很容易误解为修改了字符串对象的内容。其实不然,真实的操作则是
  1. "Hello" 是一个字符串对象,被赋给了引用 a;
  2. " world" 也是一个字符串对象,和 "Hello" 拼接生成一个新的字符串对象又被赋给了 a;
并不是 "Hello" 改变了,而是指向 "Hello" 的引用 a重新指向了新对象。

StringBuilder

StringBuilder 在很大程度上类似 ArrayList:
StringBulderArrayList
维护了一个 char 数组(其实这个数组属于它的父类 AbstractStringBuilder)维护了一个 Object 数组
append 方法向后面增加新元素add(E e) 方法向后面增加新元素
insert 方法向中间某位置插入新元素add(int index, E e) 向某位置增加新元素
deleteCharAt(int index) 删除某位置的元素remove(int index) 删除某位置的元素
添加元素时候空间不够会动态扩容
就罗列这么多吧~
很明显如果需要连续拼接很多字符串的话 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: returnpublic 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>":()V10: aload_111: invokevirtual #5// Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;14: iconst_315: invokevirtual #6// Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;18: bipush12020: invokevirtual #7// Method java/lang/StringBuilder.append:(C)Ljava/lang/StringBuilder;23: iconst_124: invokevirtual #8// Method java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder;27: ldc #9// String 229: 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_136: new #3// class java/lang/StringBuilder39: dup40: invokespecial #4// Method java/lang/StringBuilder."<init>":()V43: aload_144: invokevirtual #5// Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;47: bipush849: invokevirtual #6// Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;52: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;55: astore_156: new #3// class java/lang/StringBuilder59: dup60: invokespecial #4// Method java/lang/StringBuilder."<init>":()V63: aload_164: invokevirtual #5// Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;67: ldc #11 // String tt69: 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_276: getstatic #12 // Field java/lang/System.out:Ljava/io/PrintStream;79: aload_280: invokevirtual #13 // Method java/io/PrintStream.println:(Ljava/lang/String;)V83: return} 
  1. 在第一次拼接的时候(连续加号),先创建了一个 StringBuilder 对象,然后 append 当前的字符串对象 aa,接着连续 append 将要拼接的元素,最后 toString() 返回拼接后的字符串对象赋给 aa;
  2. 第二次拼接,同样是创建一个 StringBuilder 对象,然后 append 当前的字符串对象 aa,接着 append 8,最后 toString() 返回拼接后的字符串对象赋给 aa;
  3. 第三次拼接,同样是创建一个 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;}}输出:2224316耗时比较,前者呈指数级增长,而后者是线性增长。性能上相差甚远。甚至如果我们已经知道了容量,还可以继续优化,一次性分配一个 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---1662---3147---1563---3147---31提前分配,耗时更短~原则很简单:不要使用字符串连接操作符来合并多个字符串,除非性能无关紧要。应该使用 StringBuilder 的 append 方法。本文永久更新链接地址:http://www.linuxidc.com/Linux/2016-09/135255.htm