String、StringBuilder和StringBuffer的区别

来源:互联网 发布:小程序第三方平台源码 编辑:程序博客网 时间:2024/06/03 20:39

String、StringBuilder和StringBuffer三个类都是用于操作字符串的java.lang包下的工具类。在字符串连接操作比较频繁情况下,StringBuilder、StringBuffer代替“+”操作符能带来更好的性能。另外,StringBuffer在多线程环境下比StringBuilder更安全,因为它给大部分方法加上了synchronized锁关键字,而在单线程环境下StringBuilder比StringBuffer性能更好,因为线程不必等待获取对象锁。此外,在结构上具有的共性、差异如图所示。
这里写图片描述

String

String类是编程中使用最频繁的工具类,以至于JVM将String类的实例创建和操作都进行了特殊处理,如”aaa”直接创建一个String实例,而不用通过new String构造函数的方式进行,此外还可以通过”+”连接符直接将两个字符串进行拼接,同时创建第三个字符串实例,同时进行了拼接和创建两个操作,如下面的示例代码所示。

public static void main(String[] args) {    final String str1 = new String("Hello");    final String str2 = String.valueOf("World");    System.out.println(str1 + "," + str2);}

三个字符串实例str1、str2和”,”通过内置的“+”连接(每一次连接都会创建一个全新的对象),最终在创建一个全新的字符串实例“Hello, World”。在上面的字符串实例创建过程有三种方式:new、String.valueOf工厂方法和字符串字面量等。字面量是我们平常编程使用最广泛的方式,但也只是在字符内容确定前提下,而类似于toString的方法创建字符串又很容易忽视NullPointerException检查,利用String.valueOf静态工厂方法来创建则可以获取到一个字符串,但对于Null值获取到的是一个”null”,这有时候会造成一定的程序问题,例如字符转成数值类型。

  • 内部实现
    String作为java.lang包中的工具类,它所操作的是字符char,而char是作为基本类型在JVM中存在,也就是char是一个基本单元,同时Character才是char所对应的包装类型。String操作的是char数组,这点在String的内部封装中可以看见,并且使用final修饰,也就是在一开始赋值以后便不能再变动。
public final String     implements Serializable, Comparable<String>, CharSequence {    private final char value[];    private int hash;    private static final long seriaVersionUID = -68497944..;    private static final ObjectStreamField[] serialPersistentFields =        new ObjectStreamField[0];    public String() {        this.value = new char[0];    }    public void getChars(int paramInt1, int paramInt2,         char[] paramArrayOfChar, int paramInt3) {        if (paramInt1 < 0) {          throw new StringIndexOutOfBoundsException(paramInt1);        }        if (paramInt2 > this.count) {          throw new StringIndexOutOfBoundsException(paramInt2);        }        if (paramInt1 > paramInt2) {          throw new StringIndexOutOfBoundsException(              paramInt2 - paramInt1);        }        //内部操作时通过System.arraycopy的底层native方法实现        System.arraycopy(this.value, this.offset + paramInt1,              paramArrayOfChar, paramInt3, paramInt2 - paramInt1);     }  }... ... ...

数组是一个类型一致的数据的聚合对象,而该对象是有序的、个数已知的、不可改变的对象,所以String对象是一个值不可变的对象。String的连接操作时合并数组,重现创建一个更长数组,以及String对象的过程,所以如果“+”太频繁时,会带来性能消耗。

  • 内存分析
    一个String对象的大小由value字段决定,它的长短就是一个char[]的大小。上面的字符连接操作在内存中的过程如下图所示。这其中$1、$2是一个匿名的内部指针命名,当然是我为了说明方便而加。

这里写图片描述

StringBuilder

在23中设计模式中,建造者Builder模式是创建对象的一种模式,它可以将复杂的对象创建过程隐藏,而只暴露给客户端一个调用接口。那么StringBuilder是否就是String的建造模式的具体实现?StringBuilder是采用char[16]数组作为默认存储容器,在append操作时会动态扩展内部数组,从而实现字符串的拼接,如下面代码所示。

public static void main(String[] args) {    final String str1 = "hello";    final String str2 = "world"    final StringBuilder strBuilder = new StringBuilder();    strBuilder.append(str1).append(",").append(str2);}
  • 内部实现
    StringBuilder作为java.lang包中的工具类,它所操作的也是char。StringBuilder内部也是通过char[]数组作为字符串的存储容器,但不同于String工具类,它的char[]是可以动态扩展,也即char[]的长度在字符不断进行append时进行增加。StringBuilder内部实现如下所示。
public class StringBuilder extends AbstractStringBuilder    implements Serializable, CharSequence {    public StringBuilder() {        super(16);    }    public StringBuilder(int capacity) {        super(capacity)    }    public StringBuilder(CharSequence paramCharSequence) {        this(paramCharSequence.length() + 16);        append(paramCharSequence);    }    public StringBuilder append(String paramString) {        super.append(paramString);        return this;    }    // ... ... ...}//StringBuilder、StringBuffer的父类abstract class AbstractStringBuilder     implements Appendable, CharSequence {    char[] value;    int count;    static final int[] sizeTable = {9, 99, 999, 9999, 99999,         999999, 9999999, 99999999, 999999999, 2147483647};    AbstractStringBuilder(int paramInt) {        this.value = new char[paramInt];    }    void expandCapacity(int paramInt) {        int i = (this.value.length + 1) * 2;        if (i < 0)           i = 2147483647;        else if (paramInt > i) {           i = paramInt;        }        char[] arrayOfChar = new char[i];        System.arraycopy(this.value, 0, arrayOfChar, 0, this.count);        this.value = arrayOfChar;    }    public AbstractStringBuilder append(String paramString) {        if(paramString == null) paramString = "null";        int i = paramString.length();        if(i == 0) return this;        int j = this.count + i;        if(j > this.value.length) {            expandCapacity(j);        }        paramString.getChars(0, i, this.value, this.count);        this.count = j;        return this;    }    //... ... ... }

在进行append操作时,AbstractStringBuilder的append是StringBuilder的底层实现,其逻辑基础是附加数组前提是判断当前value是否能够容纳paramString,如果value.length长度不够,则调用expandCapacity扩展value,然后再有native方法System.arraycopy将paramString的值填充到value字符数组中,这也是StringBuilder附加字符串的逻辑实现。此外,StringBuilder不同于String工具类的根本原因在于AbstractStringBuilder的value是非终结的字段,而String的value是final修饰的终结字段,所以一个可以扩展而另一个不能扩展。

StringBuffer

StringBuilder可以动态的附加字符到内部存储容器char[]中,但是它的各项操作都是非线程安全的,在多线程环境下,可能会抛出意想不到异常,如下面代码所示。

public static void main(String[] args) {    final StringBuilder strBuilder = new StringBuilder();               //final StringBuffer strBuffer = new StringBuffer();    new Thread(new Runnable() {        public void run() {            for(int i = 1; i <= 10000; i++) {                //多线程不安全操作                strBuilder.append(i);                System.out.print(strBuilder.charAt(i)+ " ");                //线程安全操作                //strBuffer.append(i);                //System.out.print(strBuffer.charAt(i)+ " ");            }        }                   }).start();             new Thread(new Runnable() {        public void run() {            for(int i = 1; i <= 10000; i++) {                strBuilder.append(i);                System.out.print(strBuilder.charAt(i)+ " ");                //线程安全操作                //strBuffer.append(i);                //System.out.print(strBuffer.charAt(i)+ " ");            }        }    }).start();}

在运行该段代码时,抛出异常的可能性很大,大概的异常信息为下面所示。而造成异常的原因主要是append操作和chaAt操作非原子操作,它们在两个线程里存在潜在的污染(异步性),在第一个线程append时检测value的容量不够,进行扩展,但还未扩展完成时,第二个线程已经检测到改value的容量足够,所以chatAt获取字符时报错。

Exception in thread "Thread-0" java.lang.StringIndexOutOfBoundsException:     String index out of range: 1at java.lang.AbstractStringBuilder.charAt(    AbstractStringBuilder.java:177)at TestStringBuilder$1.run(TestStringBuilder.java:11)at java.lang.Thread.run(Thread.java:595)
  • 内部实现
    StringBuffer可以保证在多线程环境下附加字符串不会抛出ArrayIndexOutOfException问题,而它保证的前提是操作的原子性,也即方法加上synchronized关键字,其内部代码如下所示。
public final class StringBuffer extends AbstracStringBuilder    implements Serializable,CharSequence {    private static final ObjectStreamField[] serialPersisentFields =         { new ObjectStreamField("value", [C.class),           new ObjectStreamField("count", Integer.TYPE),           new ObjectStreamField("shared", Boolean.TYPE) };    public StringBuffer() {        super(16);    }    public StringBuffer(int capacity) {        super(capacity);    }    public synchronized char charAt(int paramInt) {        if ((paramInt < 0) || (paramInt >= this.count))           throw new StringIndexOutOfBoundsException(paramInt);        return this.value[paramInt];    }    public synchronized StringBuffer append(String paramString) {        super.append(paramString);        return this;    }    ... ... ...}

结论

String、StringBuilder和StringBuffer都是操作字符的工具类,或者String对象是大小固定的、StringBuilder和StringBuffer对象是大小可扩展的工具类。在字符串使用上String是最常见的工具类,但在字符拼接比较频繁的环境下,StringBuilder具有较高的性能,而在多线程环境下,StringBuffer比StringBuilder安全。

原创粉丝点击