java中String、StringBuffer、StringBuilder的区别

 

Java里面提供了String,StringBuffer和StringBuilder三个类来封装字符串

0. 不当用法

String result = "";  
for (String s : hugeArray) {  
    result = result + s;  
}  

不要使用String类的"+"来进行频繁的拼接,因为那样的性能极差的,应该使用StringBuffer或StringBuilder类,这在Java的优化上是一条比较重要的原则

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[];//final类型char数组
//省略其他代码……
……
}

因为有“final”修饰符,所以可以String类封装的字符串是不可变的。那么增删改是怎么实现的呢?下面是字符串截取的关键源码

public String substring(int beginIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        int subLen = value.length - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
       //当对原来的字符串进行截取的时候(beginIndex >0),返回的结果是新建的对象
        return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
    }

可以看到截取源码是生成了一个新的String对象返回的。

所以,在对String类型的字符串进行大量“插入”和“删除”操作时会产生大量的临时变量。

既然String类封装的字符串是不可变的,那么是否应该有一个类来封装可变数组。答案是有的,StringBuilder与StringBuffer封装类都是可变的。

2. 如何做到封装数组可变

StringBuilder与StringBuffer都继承自AbstractStringBuilder抽象类,在AbstractStringBuilder中也是使用字符数组保存字符串,其关键代码如下

abstract class AbstractStringBuilder implements Appendable, CharSequence {
    /**
     * The value is used for character storage.
     */
    char[] value;//一个char类型的数组,非final类型,这一点与String类不同

    /**
     * This no-arg constructor is necessary for serialization of subclasses.
     */
    AbstractStringBuilder() {
    }

    /**
     * Creates an AbstractStringBuilder of the specified capacity.
     */
    AbstractStringBuilder(int capacity) {
        value = new char[capacity];//构建了长度为capacity大小的数组
    }

//其他代码省略……
……
}

StringBuffer类实现代码如下

public final class StringBuffer
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
{
   /**
     * Constructs a string buffer with no characters in it and an
     * initial capacity of 16 characters.
     */
    public StringBuffer() {
        super(16);//创建一个默认大小为16的char型数组
    }

    /**
     * Constructs a string buffer with no characters in it and
     * the specified initial capacity.
     *
     * @param      capacity  the initial capacity.
     * @exception  NegativeArraySizeException  if the {@code capacity}
     *               argument is less than {@code 0}.
     */
    public StringBuffer(int capacity) {
        super(capacity);//自定义创建大小为capacity的char型数组
    }
//省略其他代码……
……

可以看到StringBuffer创建字符串对象默认的大小是16,当然也可以传入数组大小。

下面列下StringBuffer的用法

public class Test{
  public static void main(String args[]){
    StringBuffer sBuffer = new StringBuffer("hello");
    sBuffer.append(" ");
    sBuffer.append("world");
    System.out.println(sBuffer); 
  }
}

输出

hello world

下面看源码分析下append函数,来看下如何做到长度可变

public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
       //调用下面的ensureCapacityInternal方法
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }

private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0)
           //调用下面的expandCapacity方法实现“扩容”特性
            expandCapacity(minimumCapacity);
    }

   /**
     * This implements the expansion semantics of ensureCapacity with no
     * size check or synchronization.
     */
    void expandCapacity(int minimumCapacity) {
       //“扩展”的数组长度是按“扩展”前数组长度的2倍再加上2 byte的规则来扩展
        int newCapacity = value.length * 2 + 2;
        if (newCapacity - minimumCapacity < 0)
            newCapacity = minimumCapacity;
        if (newCapacity < 0) {
            if (minimumCapacity < 0) // overflow
                throw new OutOfMemoryError();
            newCapacity = Integer.MAX_VALUE;
        }
        //将value变量指向Arrays返回的新的char[]对象,从而达到“扩容”的特性
        value = Arrays.copyOf(value, newCapacity);
    }

可以看到空间扩展室友copyOf函数实现:

public static char[] copyOf(char[] original, int newLength) {
        //创建长度为newLength的char数组,也就是“扩容”后的char 数组,并作为返回值
        char[] copy = new char[newLength];
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;//返回“扩容”后的数组变量
    }

3. StringBuilder与StringBuffer 区别

AbstractStringBuilder是StringBuilder与StringBuffer的公共父类,定义了一些字符串的基本操作,如expandCapacity、append、insert、indexOf等公共方法,StringBuilder和StringBuffer的方法实现基本上一致,不同的是StringBuffer类的方法前多了个synchronized关键字,即StringBuffer是线程安全的。

public synchronized StringBuffer reverse() {
    super.reverse();
    return this;
}

public int indexOf(String str) {
    return indexOf(str, 0);        //存在 public synchronized int indexOf(String str, int fromIndex) 方法
}

(1)append,insert,delete方法最根本上都是调用System.arraycopy()这个方法来达到目的

(2)substring(int, int)方法是通过重新new String(value, start, end - start)的方式来达到目的。因此,在执行substring操作时,StringBuilder和String基本上没什么区别。

4. 使用场景

如果是多线程环境下涉及到共享变量的插入和删除操作,StringBuffer则是首选。如果是非多线程操作并且有大量的字符串拼接,插入,删除操作则StringBuilder是首选。毕竟String类是通过创建临时变量来实现字符串拼接的,耗内存还效率不高,怎么说StringBuilder是通过JNI方式实现终极操作的。

5. 总结

  • String类型的字符串对象是不可变的,一旦String对象创建后,包含在这个对象中的字符系列是不可以改变的,直到这个对象被销毁。
  • StringBuilder和StringBuffer类型的字符串是可变的,不同的是StringBuffer类型的是线程安全的,而StringBuilder不是线程安全的
  • 如果是多线程环境下涉及到共享变量的插入和删除操作,StringBuffer则是首选。如果是非多线程操作并且有大量的字符串拼接,插入,删除操作则StringBuilder是首选。
  • 如果要操作少量的数据,用String;单线程操作大量数据,用StringBuilder;多线程操作大量数据,用StringBuffer。
posted @ 2018-01-01 17:40  jihite  阅读(737)  评论(0编辑  收藏  举报