Java 中的StringStringBuilder与StringBuffer的区别联系(转载)
Posted 知行合一
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 中的StringStringBuilder与StringBuffer的区别联系(转载)相关的知识,希望对你有一定的参考价值。
1 String 基础
想要了解一个类,最好的办法就是看这个类的源代码,String类源代码如下:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final char value[]; /** Cache the hash code for the string */ private int hash; // Default to 0 /** use serialVersionUID from JDK 1.0.2 for interoperability */ private static final long serialVersionUID = -6849794470754667710L; /** * Class String is special cased within the Serialization Stream Protocol. * * A String instance is written into an ObjectOutputStream according to * <a href="{@docRoot}/../platform/serialization/spec/output.html"> * Object Serialization Specification, Section 6.2, "Stream Elements"</a> */ private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];
从上面代码可以看出:
① String类是final类,即意味着String类不能被继承,并且它的成员方法都默认为final方法。
② 上面列出了String类的成员属性,String类其实是通过char数组来保存字符串的。
再来看String类的一些方法:
public String substring(int beginIndex, int endIndex) { if (beginIndex < 0) { throw new StringIndexOutOfBoundsException(beginIndex); } if (endIndex > value.length) { throw new StringIndexOutOfBoundsException(endIndex); } int subLen = endIndex - beginIndex; if (subLen < 0) { throw new StringIndexOutOfBoundsException(subLen); } return ((beginIndex == 0) && (endIndex == value.length)) ? this : new String(value, beginIndex, subLen); } public String concat(String str) { int otherLen = str.length(); if (otherLen == 0) { return this; } int len = value.length; char buf[] = Arrays.copyOf(value, len + otherLen); str.getChars(buf, len); return new String(buf, true); }
public String replace(char oldChar, char newChar) { if (oldChar != newChar) { int len = value.length; int i = -1; char[] val = value; /* avoid getfield opcode */ while (++i < len) { if (val[i] == oldChar) { break; } } if (i < len) { char buf[] = new char[len]; for (int j = 0; j < i; j++) { buf[j] = val[j]; } while (i < len) { char c = val[i]; buf[i] = (c == oldChar) ? newChar : c; i++; } return new String(buf, true); } } return this; }
上面三个方法,无论是substring、concat、replace操作都不是在原来的基础上进行的,而是重新生成了一个新的字符串对象。也就是说进行这些操作后,最原始的字符串没有改变。
牢记: 对String对象的任何改变都不会影响到原对象,相关的任何change操作都会产生新的对象。
2 深入理解String、StringBuffer、StringBuilder
(1) String str = "Hello" 与 String str = new String("Hello") 的区别
1 String str1="Hello latiny"; 2 String str2="Hello latiny"; 3 String str3=new String("Hello latiny"); 4 String str4=new String("Hello latiny"); 5 6 System.out.println(str1==str2); 7 System.out.println(str1==str3); 8 System.out.println(str3==str4);
结果为:
true false false
为什么会出现这样的结果?这里我们作一个详细解释
① 首先得引入常量池的概念,常量池指的是在编译期就被确定,并保存在已编译的.class文件中的一些数据。它包含了类、方法、接口等的常量,也包含了字符串常量。
String str1="Hello latiny"
String str2="Hello latiny";
其中 Hello latiny 都是字符串常量。它们在编译时就被确定了,所以str1 == str2为true
② 用new String()创建的字符串不是常量,不能在编译时确定,所以new String() 创建的字符串不放入常量池中,它们有自己的地址空间。更进一步解释一下,通过new 关键字创建的对象是在堆区进行的,而在堆区进行对象的生成过程是不会去检测该对象是否已经存在。因此通过new创建的对象,一定是不同的对象,即使字符串内容相同。
(2) 既然在Java中已经存在了String类,为什么还需要StringBuffer与StringBuilder类呢?
看下面这段代码:
1 public class StringTest { 2 3 public static void main(String[] args) { 4 5 String str = ""; 6 for(int i=0; i<10000; i++) 7 { 8 str+="Hello"; 9 } 10 11 } 12 13 }
str+="Hello"; 这句代码的过程相当于将原有的 str 变量指向的对象内容取出与 Hello 作字符串相加操作,再存进另一个新的String 对象中,再让str变量的指向新生成的对象。反编译其字节码文件就知道了:
C:\\Work\\Project\\Java\\Eclipse\\JustTest\\bin\\com\\latiny\\string>javap -c StringTest 警告: 二进制文件StringTest包含com.latiny.string.StringTest Compiled from "StringTest.java" public class com.latiny.string.StringTest { public com.latiny.string.StringTest(); Code: 0: aload_0 1: invokespecial #8 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: ldc #16 // String 2: astore_1 3: iconst_0 4: istore_2 5: goto 31 8: new #18 // class java/lang/StringBuilder 11: dup 12: aload_1 13: invokestatic #20 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String; 16: invokespecial #26 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V 19: ldc #29 // String Hello 21: invokevirtual #31 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 24: invokevirtual #35 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 27: astore_1 28: iinc 2, 1 31: iload_2 32: sipush 10000 35: if_icmplt 8 38: return }
从这段反编译的字节码文件可以看出:从第8行到35行是整个循环执行过程, 并且每次循环都会new 出一个StringBuilder对象,然后进行append操作,最后通过toString 方法返回String对象。也就是说这个循环执行完毕new出了10000个对象, 这是非常大的资源浪费。从上面还可以看出:str+="Hello"; 自动会被JVM优化成:
StringBuilder str1 = new StringBuilder(str);
str1.append("Hello");
str = str1.toString();
再来看下面这段代码:
public class StringTest { public static void main(String[] args) { StringBuilder str = new StringBuilder(); for(int i=0; i<10000; i++) { str.append("Hello"); } } }
反编译字节码文件得到如下代码:
C:\\Work\\Project\\Java\\Eclipse\\JustTest\\bin\\com\\latiny\\string>javap -c StringTest 警告: 二进制文件StringTest包含com.latiny.string.StringTest Compiled from "StringTest.java" public class com.latiny.string.StringTest { public com.latiny.string.StringTest(); Code: 0: aload_0 1: invokespecial #8 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: new #16 // class java/lang/StringBuilder 3: dup 4: invokespecial #18 // Method java/lang/StringBuilder."<init>":()V 7: astore_1 8: iconst_0 9: istore_2 10: goto 23 13: aload_1 14: ldc #19 // String Hello 16: invokevirtual #21 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 19: pop 20: iinc 2, 1 23: iload_2 24: sipush 10000 27: if_icmplt 13 30: return }
上面代码可以看出,循环从13行到27行结束,并且new 操作只进行了一次,也就是说只产生了一个对象, append操作是在原有对象的基础上进行的。因此循环10000次之后,这段代码所占的资源比直接使用String定义的变量要小得多
那有人又要问了既然有了StringBuilder类,为什么还需要StringBuffer类,查看源代码就知道了,StringBuilder与StringBuffer类拥有的成员属性及方法基本相同,区别是StringBuffer类的成员方法多了一个关键字: synchronized,很明显这个关键字是在多线程访问时起到安全保护作用的,即StringBuffer是线程安全的。
StringBuilder的insert方法:
1 @Override 2 public StringBuilder insert(int offset, Object obj) { 3 super.insert(offset, obj); 4 return this; 5 }
StringBuffer的insert方法:
1 @Override 2 public synchronized StringBuffer insert(int index, char[] str, int offset, 3 int len) 4 { 5 toStringCache = null; 6 super.insert(index, str, offset, len); 7 return this; 8 }
3 三个类不同场景的性能测试
public class StringTest { private static final int TIMES = 50000; public static void main(String[] args) { TestString1(); TestBuilder(); TestBuffer(); // 直接字符相加与间接字符相加对比 TestString2(); TestString3(); } public static void TestString1() { String str = ""; long begin = System.currentTimeMillis(); for(int i=0; i<TIMES; i++) { str += "Java"; } long end = System.currentTimeMillis(); System.out.println("操作"+str.getClass().getName()+"类型需要的时间:"+(end-begin)+"毫秒"); } public static void TestBuilder() { StringBuilder str = new StringBuilder(); long begin = System.currentTimeMillis(); for(int i=0; i<TIMES; i++) { str.append("Java"); } long end = System.currentTimeMillis(); System.out.println("操作"+str.getClass().getName()+"类型需要的时间:"+(end-begin)+"毫秒"); } public static void TestBuffer() { StringBuffer str = new StringBuffer(); long begin = System.currentTimeMillis(); for(int i=0; i<TIMES; i++) { str.append("Java"); } long end = System.currentTimeMillis(); System.out.println("操作"+str.getClass().getName()+"类型需要的时间:"+(end-begin)+"毫秒"); } public static void TestString2() { String str = ""; long begin = System.currentTimeMillis(); for(int i=0; i<TIMES; i++) { str="I"+"Love"+"Java"; } long end = System.currentTimeMillis(); System.out.println("字符串直接相加操作需要的时间:"+(end-begin)+"毫秒"); } public static void TestString3() { String str1 = "I"; String str2 = "Love"; String str3 = "Java"; String str = ""; long begin = System.currentTimeMillis(); for(int i=0; i<TIMES; i++) { str = str1+str2+str3; } long end = System.currentTimeMillis(); System.out.println("字符串间接相加操作需要的时间:"+(end-begin)+"毫秒"); } }
测试环境:win7 + Eclipse + JDK1.8
结果为:
操作java.lang.String类型需要的时间:5395毫秒
操作java.lang.StringBuilder类型需要的时间:1毫秒
操作java.lang.StringBuffer类型需要的时间:2毫秒
字符串直接相加操作需要的时间:1毫秒
字符串间接相加操作需要的时间:4毫秒
请按任意键继续. . .
上面提到JVM会自动优化 str+="Hello",看如下代码:
public static void TestString1() { String str = ""; long begin = System.currentTimeMillis(); for(int i=0; i<time; i++) { str+="Java"; } long end = System.currentTimeMillis(); System.out.println("操作"+str.getClass().getName()+"类型需要的时间:"+(end-begin)+"毫秒"); } public static void TestString1Optimal() { String str = ""; long begin = System.currentTimeMillis(); for(int i=0; i<time; i++) { StringBuilder str1 = new StringBuilder(str); str1.append("Java"); str = str1.toString(); } long end = System.currentTimeMillis(); System.out.println("模拟JVM优化操作需要的时间:"+(end-begin)+"毫秒"); }
执行结果:
操作java.lang.String类型需要的时间:11692毫秒
模拟JVM优化操作需要的时间:9958毫秒
得到验证。
对执行结果进行一般的解释:
① 对于直接相加字符串,效率很高,因为在编译器便确定了它的值,也就是说形如 "I"+"like"+"Java" 的字符串相加,在编译时被优化成 "Ilikejava"。对于间接相加(即包含字符串引用),刑如:str=str1+str2+str3 ,效率比直接相加低,因为在编译时编译器不会对引用变量进行优化。
② 三者的效率:
StringBuilder > StringBuffer > String
但是这只是相对的,如String str = "Hello" + "Latiny"; 比 StringBuilder str = new StringBuilder().append("Hello").append("Latiny"); 要高。
这三个类各有利弊,根据不同的情况选择使用:
当字符串相加操作或者改动较少时,使用String类;
当字符串相加操作较多时,使用StringBuilder吗,如果采用多线程,需要考虑线程安全则使用StringBuffer;
4 常见的String、 StringBuilder、StringBuffer面试题
(1) String a = "Hello2"; final String b = "Hello"; String c = b+"2"; System.out.println(a==c); 输出结果为true
(2) 下面代码输出结果为false:
1 public static void main(String[] args) { 2 // TODO Auto-generated method stub 3 //TestString1(); 4 //TestString1Optimal(); 5 String a = "Hello2"; 6 final String b = getHello(); 7 String c = b+"2"; System.out.println(a==c); 8 } 9 10 public static String getHello() 11 { 12 return "Hello"; 13 }
(3) 下面代码输出为true:
1 public static void main(String[] args) { 2 // TODO Auto-generated method stub 3 //TestString1(); 4 //TestString1Optimal(); 5 String a = "Hello2"; 6 String b = a.intern(); 7 System.out.println(a==b); 8 }
(4) 代码 I 与 II 的区别
1 String str = "I"; 2 str += "like" + "Java"; //I 3 //str = str+"like" + "Java"; //II
① I的效率比II 高,I的 "like" + "Java" 在编译时会被优化为 likejava 而II 的不会
I 的反编译字节码
public class com.latiny.string.StringTest1 { public com.latiny.string.StringTest1(); Code: 0: aload_0 1: invokespecial #8 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: ldc #16 // String I 2: astore_1 3: new #18 // class java/lang/StringBuilder 6: dup 7: aload_1 8: invokestatic #20 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String; 11: invokespecial #26 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V 14: ldc #29 // String likeJava 16: invokevirtual #31 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 19: invokevirtual #35 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 22: astore_1 23: return }
II 的反编译字节码
public class com.latiny.string.StringTest1 { public com.latiny.string.StringTest1(); Code: 0: aload_0 1: invokespecial #8 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: ldc #16 // String I 2: astore_1 3: new #18 // class java/lang/StringBuilder 6: dup 7: aload_1 8: invokestatic #20 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String; 11: invokespecial #26 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V 14: ldc #29 // String like 16: invokevirtual #31 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 19: ldc #35 // String Java 21: invokevirtual #31 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 24: invokevirtual #37 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 27: astore_1 28: return }
可以看出 I 中只进行了一次append操作,II 的进行了两次。
转自:http://www.cnblogs.com/dolphin0520/p/3778589.html
以上是关于Java 中的StringStringBuilder与StringBuffer的区别联系(转载)的主要内容,如果未能解决你的问题,请参考以下文章