Java 中的String、StringBuilder与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 int time=50000;

    public static void main(String[] args) {
        TestString1();
        TestString2();
        TestString3();
        TestBuilder();
        TestBuffer();
    }

    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  TestBuilder()
    {
        StringBuilder str = new StringBuilder();
        long begin = System.currentTimeMillis();
        for(int i=0; i<time; 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<time; 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<time; i++)
        {
            str="I"+"like"+"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<time; i++)
        {
            str=str1+str2+str3;
        }
        long end = System.currentTimeMillis();

        System.out.println("字符串间接相加操作需要的时间:"+(end-begin)+"毫秒");
    }

}

  测试环境:win7 + Eclipse + JDK1.8

  结果为:

操作java.lang.String类型需要的时间:11106毫秒
字符串直接相加操作需要的时间:2毫秒
字符串间接相加操作需要的时间:22毫秒
操作java.lang.StringBuilder类型需要的时间:10毫秒
操作java.lang.StringBuffer类型需要的时间:7毫秒

  上面提到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

原文地址:https://www.cnblogs.com/Latiny/p/8289523.html

时间: 2024-10-11 14:10:38

Java 中的String、StringBuilder与StringBuffer的区别联系(转载)的相关文章

String, StringBuilder及StringBuffer的区别

在java中,String, StringBuilder及StringBuffer经常被用来处理字符串操作. 下表列出它们的异同点: String StringBuffer StringBuilder 是否可继承 否(final) 否(final) 否(final) 是否长度可变 否 是 是 是否线程安全 ---------- 是 否 拼接效率 低 中 高 一般情况下,使用String,StringBuilder比较多.因为字符串的拼接很少会涉及到多线程,所以StringBuffer用的较少.先

String, StringBuilder 与StringBuffer的区别与联系

1.区别 (1)String构建的对象不能改变,每次对String进行操作时,如两个String相加,需要新建一个String对象,然后容纳最终的结果. 而StringBuilder与StringBuffer构建的对象可以随时在修改其内容,而无需生成新的对象.一般新建一个对象是会生成16个字节的空间,之后根据需要再增加空间. 由于一般新构建一个对象涉及分配内存空间分配.无引用对象过多时的垃圾回收等,因此,对于操作频繁的字符串需使用StringBuilder或StringBuffer. (2)St

Java中的String类与StringBuffer类

String和StringBuffer主要有2个区别: (1)String类对象为不可变对象,一旦你修改了String对象的值,隐性重新创建了一个新的对象,释放原String对象,StringBuffer类对象为可修改对象,可以通过append()方法来修改值 (2)String类对象的性能远不如StringBuffer类. 具体解释如下: 在java中有3个类来负责字符的操作. 1.Character 是进行单个字符操作的, 2.String 对一串字符进行操作.不可变类. 3.StringB

浅析Java中Map与HashMap,Hashtable,HashSet的区别(转载)

HashMap和Hashtable两个类都实现了Map接口,二者保存K-V对(key-value对):HashSet则实现了Set接口,性质类似于集合 HashTable和HashMap区别 第一,继承的父类不同.Hashtable继承自Dictionary类,而HashMap继承自AbstractMap类.但二者都实现了Map接口. public class Hashtable<K,V>extends Dictionary<K,V>implements Map<K,V>

重温java中的String,StringBuffer,StringBuilder类

任何一个系统在开发的过程中, 相信都不会缺少对字符串的处理. 在 java 语言中, 用来处理字符串的的类常用的有 3 个: String.StringBuffer.StringBuilder. 它们的异同点: 1) 都是 final 类, 都不允许被继承; 2) String 长度是不可变的, StringBuffer.StringBuilder 长度是可变的; 3) StringBuffer 是线程安全的, StringBuilder 不是线程安全的. String 类已在上一篇随笔 小瓜牛

Java中的String,StringBuilder,StringBuffer三者的区别

最近在学习Java的时候,遇到了这样一个问题,就是String,StringBuilder以及StringBuffer这三个类之间有什么区别呢,自己从网上搜索了一些资料,有所了解了之后在这里整理一下,便于大家观看,也便于加深自己学习过程中对这些知识点的记忆,如果哪里有误,恳请指正. 这三个类之间的区别主要是在两个方面,即运行速度和线程安全这两方面. 首先说运行速度,或者说是执行速度,在这方面运行速度快慢为:StringBuilder > StringBuffer > String String

深刻理解Java中的String、StringBuffer和StringBuilder的区别

首先简单地来梳理一下Java中String.StringBuffer和StringBuilder各自的含义. 1.String类 首先,它是线程安全的,即可以用于多线程编程中: 其次,String类的对象是不可变的,即在定义时就确定了,类似String str="Hello";str+="Java";的语句其实是生成了新的对象,只是我们未察觉到而已.但是注意在大量的字符串新建对象时消耗就很可观,这时必须考虑采用StringBuffer或StringBuilder,否

[转载]Java中的String,StringBuilder,StringBuffer三者的区别

最近在学习Java的时候,遇到了这样一个问题,就是String,StringBuilder以及StringBuffer这三个类之间有什么区别呢,自己从网上搜索了一些资料,有所了解了之后在这里整理一下,便于大家观看,也便于加深自己学习过程中对这些知识点的记忆,如果哪里有误,恳请指正. 这三个类之间的区别主要是在两个方面,即运行速度和线程安全这两方面. 首先说运行速度,或者说是执行速度,在这方面运行速度快慢为:StringBuilder > StringBuffer > String String

分享知识-快乐自己:Java 中 的String,StringBuilder,StringBuffer三者的区别

这三个类之间的区别主要是在两个方面,即运行速度和线程安全这两方面. 1):首先说运行速度,或者说是执行速度,在这方面运行速度快慢为:StringBuilder > StringBuffer > String String:最慢的原因: String:为字符串常量,而StringBuilder和StringBuffer均为字符串变量,即String对象一旦创建之后该对象是不可更改的,但后两者的对象是变量,是可以更改的. 以下面一段代码为例: String str="abc";