Java中String,StringBuilder和StringBuffer的区别

来源:互联网 发布:施工进度图绘制软件 编辑:程序博客网 时间:2024/09/21 09:19

欢迎关注本人公众号


大家都知道,String对象是不可变的,不可变性会带来效率问题。为String对象重载“+”操作符时,都会自动生成一个新的String对象。
也有人说,String在上述的问题中已经会自动引入StringBuilder来解决效率问题。


为此,我在《Java编程思想》中找到了答案。


第一个小例子:

[java] view plaincopy
  1. package com.linc.TestString;  
  2.   
  3. public class TestString {  
  4.     public static void main(String[] args)  
  5.     {  
  6.         String mango = "mango";  
  7.         String someting = "abc" + mango + "def" + 47;  
  8.         System.out.println(someting);  
  9.     }  
  10. }  
执行编译,命令:
javac TestString.java
用javap来反编译上述代码,看看都发生了什么故事:
javap -c TestString


结果如下:

[java] view plaincopy
  1. Compiled from "TestString.java"  
  2. public class com.linc.TestString.TestString extends java.lang.Object{  
  3. public com.linc.TestString.TestString();  
  4.   Code:  
  5.    0:   aload_0  
  6.    1:   invokespecial   #1//Method java/lang/Object."<init>":()V  
  7.    4:   return  
  8.   
  9. public static void main(java.lang.String[]);  
  10.   Code:  
  11.    0:   ldc #2//String mango  
  12.    2:   astore_1  
  13.    3:   new #3//class java/lang/StringBuilder  
  14.    6:   dup  
  15.    7:   invokespecial   #4//Method java/lang/StringBuilder."<init>":()V  
  16.    10:  ldc #5//String abc  
  17.    12:  invokevirtual   #6//Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;  
  18.    15:  aload_1  
  19.    16:  invokevirtual   #6//Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;  
  20.    19:  ldc #7//String def  
  21.    21:  invokevirtual   #6//Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;  
  22.    24:  bipush  47  
  23.    26:  invokevirtual   #8//Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;  
  24.    29:  invokevirtual   #9//Method java/lang/StringBuilder.toString:()Ljava/lang/String;  
  25.    32:  astore_2  
  26.    33:  getstatic   #10//Field java/lang/System.out:Ljava/io/PrintStream;  
  27.    36:  aload_2  
  28.    37:  invokevirtual   #11//Method java/io/PrintStream.println:(Ljava/lang/String;)V  
  29.    40:  return  
  30.   
  31. }  

从上述代码中看,编译器确实创建了一个StringBuilder对象。
但是,这并不能因此就说明可以随意的使用String对象。下面又举了个例子:

[java] view plaincopy
  1. package com.linc.TestString;  
  2.   
  3. public class StringAndBuilder {  
  4.     public String implicit(String[] fields)  
  5.     {  
  6.         String result="";  
  7.         for(int i=0;i<fields.length;++i)  
  8.         {  
  9.             result+=fields[i];  
  10.         }  
  11.         return result;  
  12.     }  
  13.       
  14.     public String explicit(String[] fields)  
  15.     {  
  16.         StringBuilder result=new StringBuilder();  
  17.         for(int i=0;i<fields.length;++i)  
  18.         {  
  19.             result.append(fields[i]);  
  20.         }  
  21.         return result.toString();  
  22.     }  
  23. }  

反编译一下:
[linc@localhost TestString]$ javac StringAndBuilder.java 
[linc@localhost TestString]$ javap -c StringAndBuilder

[java] view plaincopy
  1. Compiled from "StringAndBuilder.java"  
  2. public class com.linc.TestString.StringAndBuilder extends java.lang.Object{  
  3. public com.linc.TestString.StringAndBuilder();  
  4.   Code:  
  5.    0:   aload_0  
  6.    1:   invokespecial   #1//Method java/lang/Object."<init>":()V  
  7.    4:   return  
  8.   
  9. public java.lang.String implicit(java.lang.String[]);  
  10.   Code:  
  11.    0:   ldc #2//String   
  12.    2:   astore_2  
  13.    3:   iconst_0  
  14.    4:   istore_3  
  15.    5:   iload_3  
  16.    6:   aload_1  
  17.    7:   arraylength  
  18.    8:   if_icmpge   38  
  19.    11:  new #3//class java/lang/StringBuilder  
  20.    14:  dup  
  21.    15:  invokespecial   #4//Method java/lang/StringBuilder."<init>":()V  
  22.    18:  aload_2  
  23.    19:  invokevirtual   #5//Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;  
  24.    22:  aload_1  
  25.    23:  iload_3  
  26.    24:  aaload  
  27.    25:  invokevirtual   #5//Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;  
  28.    28:  invokevirtual   #6//Method java/lang/StringBuilder.toString:()Ljava/lang/String;  
  29.    31:  astore_2  
  30.    32:  iinc    31  
  31.    35:  goto    5  
  32.    38:  aload_2  
  33.    39:  areturn  
  34.   
  35. public java.lang.String explicit(java.lang.String[]);  
  36.   Code:  
  37.    0:   new #3//class java/lang/StringBuilder  
  38.    3:   dup  
  39.    4:   invokespecial   #4//Method java/lang/StringBuilder."<init>":()V  
  40.    7:   astore_2  
  41.    8:   iconst_0  
  42.    9:   istore_3  
  43.    10:  iload_3  
  44.    11:  aload_1  
  45.    12:  arraylength  
  46.    13:  if_icmpge   30  
  47.    16:  aload_2  
  48.    17:  aload_1  
  49.    18:  iload_3  
  50.    19:  aaload  
  51.    20:  invokevirtual   #5//Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;  
  52.    23:  pop  
  53.    24:  iinc    31  
  54.    27:  goto    10  
  55.    30:  aload_2  
  56.    31:  invokevirtual   #6//Method java/lang/StringBuilder.toString:()Ljava/lang/String;  
  57.    34:  areturn  
  58.   
  59. }  

注意到implicit方法,从第8行到第35行构成了一个循环体。StringBuilder是在循环体内构造的,也就是说,每经过一次循环,就会创建一个新的StrinBuilder对象。
再看explicit方法,循环部分的代码更简短、简单,而且只生成了一个StrinBuilder对象。


结论:
当为一个类编写toString()方法时,如果操作比较简单,就可以信赖编译器,它会为你合理的构造最终的字符串结果。如果使用了循环,那么最好自己创建一个StringBuilder对象。
如果拿不准该用那种方式,就用javap来分析你的程序吧!


另外,linc之前还翻译了一段小文章,是讲String、StringBuffer和StringBuilder之间的区别的:http://blog.csdn.net/lincyang/article/details/6333041

------------------------------------------------------------------------------------------------------------------------------


 可以证明,字符串操作是计算机程序设计中最常见的行为。


       Java对象的引用,用来操纵内存元素。例如:String s;//创建一个String类型的引用
对象的引用与对象关联——初始化,例如:s = "abc";//Java语言特性,String可以用带引号的文本初始化。
更通用的初始化,创建对象,例如:s = new String("abc");——《Thinking in Java》


    Java中所有的字符串文字[字符串常量]都是一个String的对象。Java中维护一个字符串常量列表(常量池,编译时决定),
存放字符创常量。当遇到字符串常量时,先检查它常量池是否存在,不存在则在堆上创建对象并将字符串常量放入常量池。
被放到字符串内部列表中的字符串对象是不会被垃圾回收的,生命周期和整个程序相同,所以如果使用不当会造成内存泄露。


    String对象具有只读特性,指向它的任何引用都不可能改变它的值。不可变性带来的是效率问题。String中的+和+=是仅有的两个重载过的操作符。Java不允许程序员重载任何操作符。 由于字符串对象的大量使用(它是一个对象,一般而言对象总是在heap分配内存),Java中为了节省内存空间和运行时间(如比较字符串时,==比equals()快),在编
译阶段就把所有的字符串文字放到一个文字池(pool of literal strings,即字符串常量列表)中,而运行时文字池成为常量池的一部分。文字池的好处,就是该池中所有相同的字符串常量被合并,只占用一个空间。


StringBuilder类,对应于String类,StringBuilder具有可变性。
StringBuilder是Java SE5引入的,线程非安全的。
StringBuffer是Java SE5之前就有的线程安全的,相对来说较StringBuilder开销稍大
两者的常用操作:append(),toString(),insert(),reverse(),delete()等。


String:不可变的对象,对String对象进行改变的时候其实都等同于生成了一个新的String对象,然后将引用指向新的String对象,原String对象GC回收。
StringBuffer 字符串变量(线程安全),适用于多线程程序中,保证同步性。
StringBuilder 字符串变量(非线程安全),适用于单线程程序中,不保证同步性。简要的说, String 类和 StringBuffer/StringBuilder 类的主要性能区别其实在于 String 是不可变的对象, 因此在每次对 String 类进行改变的   时候其实都等同于生成了一个新的 String 对象,然后将指针指向新的 String 对象,所以经常改变内容的字符串最好不要用 String ,因为每次生成   对象都会对系统性能产生影响,特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作,那速度是一定会相当慢的。   而如果是使用 StringBuffer/StringBuilder 类则结果就不一样了,每次结果都会对 StringBuffer/StringBuilder 对象本身进行操作,而不是生成   新的对象,再改变对象引用。所以在一般情况下推荐使用 StringBuffer/StringBuilder ,特别是字符串对象经常改变的情况下。而在某些特别情况下,   String 对象的字符串拼接其实是被 JVM 解释成 StringBuffer/StringBuilder 对象的拼接,所以这些时候 String 对象的速度并不会比  StringBuffer/StringBuilder 对象慢,而特别是以下的字符串对象生成中, String 效率是远要比 StringBuffer 快的:
   
   String S1 = “This is only a” + “ simple” + “ test”;
   StringBuilder Sb = new StringBuilder(“This is only a”).append(“ simple”).append(“ test”);
   你会很惊讶的发现,生成 String S1 对象的速度简直太快了,而这个时候 StringBuffer 居然速度上根本一点都不占优势。其实这是 JVM 的一个把戏,
   在 JVM 眼里,这个
   String S1 = “This is only a” + “ simple” + “test”; 其实就是:
   String S1 = “This is only a simple test”; 所以当然不需要太多的时间了。但大家这里要注意的是,如果你的字符串是来自另外的 String 对象
   的话,速度就没那么快了,譬如:
   String S2 = “This is only a”;
   String S3 = “ simple”;
   String S4 = “ test”;
   String S1 = S2 +S3 + S4;
   这时候 JVM 会规规矩矩的按照原来的方式去做


在大部分情况下 StringBuffer > String
StringBuffer
Java.lang.StringBuffer线程安全的可变字符序列。一个类似于 String 的字符串缓冲区,但可以修改。虽然在任意时间点上它都包含某
种特定的字符序列,但通过某些方法调用可以改变该序列的长度和内容。可将字符串缓冲区安全地用于多个线程。可以在必要时对这些方法进行同步,因此任意特定实例上的所有操作就好像是以串行顺序发生的,该顺序与所涉及的每个线程进行的方法调用顺序一致。StringBuffer 上的主要操作是 append 和 insert 方法,可重载这些方法,以接受任意类型的数据。每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符追加或插入到字符串缓冲区中。append 方法始终将这些字符添加到缓冲区的末端;而 insert 方法则在指定的点添加字符。例如,如果 z 引用一个当前内容是“start”的字符串缓冲区对象,则此方法调用 z.append("le") 会使字符串缓冲区包含“startle”,
而 z.insert(4, "le") 将更改字符串缓冲区,使之包含“starlet”。


   在大部分情况下 StringBuilder > StringBuffer
java.lang.StringBuilder
java.lang.StringBuilder一个可变的字符序列是5.0新增的。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用
作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。如果可能,建议优先采用该类,因为在
大多数实现中,它比 StringBuffer 要快。两者的方法基本相同。
0 0
原创粉丝点击