从Java源码角度彻底理解String,StringBuilder和StringBuffer的区别

KimMonroy 7年前
   <p>字符串,就是一系列字符的集合。</p>    <p>Java里面提供了String,StringBuffer和StringBuilder三个类来封装字符串,其中StringBuilder类是到jdk 1.5才新增的。字符串操作可以说是几乎每门编程语言中所必不可少的,你真的理解其内幕吗?</p>    <p>下面让我们开始探秘之旅吧!</p>    <p>1、既然都是用来封装字符串的,那为什么还要3个类来封装呢?</p>    <p>2、它们三者之间到底有何区别?</p>    <p>3、它们三者之间的使用场景分别是什么?</p>    <p>4、它们三者之间从内存角度来看又是怎么来实现的呢?</p>    <p>5、它们三者之间的性能效率是怎么排列的?</p>    <p>下面就让我们逐一破解这几个谜团吧!</p>    <h3><strong>一、概述</strong></h3>    <p>字符串是由若干个字符线性排列组成的,所以我们可以把字符串当作数组(Array)来看待。众所周知,数组就是内存里线性排列的有序地址空间块。既然是线性排列,有序的一组地址块,那么在分配数组空间时就必须指定空间大小也就是数组大小,这也是大多数编程语言里规定在定义数组时要指定数组大小的原因( <strong>某些编程语言中可变数组的实现另当别论</strong> )。换言之, 数组就分为可变数组和不可变数组。可变数组能够动态插入和删除,而不可变数组一旦分配好空间后则不能进行动态插入或删除操作。</p>    <h3><strong>二、从实际应用可能的场景中分析String,StringBuilder,StringBuffer产生的背景</strong></h3>    <p>在实际应用当中我们可能会对字符串经常做如下几种操作:插入,删除,修改,拼接,截取,查到,替换……其中,“插入”和“删除”操作就涉及到对 <strong>原字符串的长度</strong> 进行修改( <strong>其实,“拼接”和“截取”也分为可以理解为插入和删除操作</strong> )。</p>    <p>然而,在jdk 1.0中就出现的 <strong>String类封装的字符串是不可变的</strong> ,即不能在原字符串上进行 <strong>插入</strong> 和 <strong>删除</strong> 操作,String的API是通过新建临时变量的方式来实现字符串的插入和删除操作。因为是新建临时变量,所以 当你调用String类的API对字符串进行插入和删除操作时原来的字符串是不会有任何改变的,返回给你的是一个新的字符串对象 。关于这一点,我们将在后面通过分析源码的方式来证实!既然String类封装的是不可变数组,那么对应的就应该有一个类来封装可变数组。没错! StringBuffer类封装的就是可变数组,并且还是线程安全的。 所以,在非多线程环境下效率相对较低。正如大家所想的一样,JDK里也提供了非多线程环境下使用的可变字符串的封装类,它就是在jdk 1.5里面才姗姗来迟的StringBuilder类, <strong>StringBuilder类是非线程安全的可变字符串封装类</strong> ,也是我们今天要讨论的成员之一。</p>    <p>到此,我们基本上就已经回答了在文章开始处提出的第1,2,3个问题。接下来,我们再从源码的角度来更深入讨论。</p>    <h3><strong>三、从源码角度进一步探讨其内部实现方式</strong></h3>    <p>1、String类的关键源码分析如下:</p>    <pre>  <code class="language-java">public final class String      implements java.io.Serializable, Comparable<String>, CharSequence {      /** The value is used for character storage. */     private final char value[];//final类型char数组  //省略其他代码……  ……  }</code></pre>    <p>从上述的代码片段中我们可以看到,String类在类开始处就定义了一个final 类型的char数组value。也就是说通过 String类定义的字符串中的所有字符都是存储在这个final 类型的char数组中的。</p>    <p>下面我们来看一下String类对字符串的截取操作,关键源码如下:</p>    <pre>  <code class="language-java">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);      }</code></pre>    <p>当我们对字符串从第beginIndex(beginIndex >0) 个字符开始进行截取时,返回的结果是重新new出来的对象。所以,在对String类型的字符串进行大量“插入”和“删除”操作时会产生大量的临时变量。</p>    <p>2、StringBuffer和StringBuilder类关键源码分析:</p>    <p>在进行这两个类的源码分析前,我们先来分析下一个抽象类AbstractStringBuilder,因为,StringBuffer和StringBuilder都继承自这个抽象类,即AbstractStringBuilder类是StringBuffer和StringBuilder的共同父类。AbstractStringBuilder类的关键代码片段如下:</p>    <pre>  <code class="language-java">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大小的数组      }    //其他代码省略……  ……  }</code></pre>    <p>StringBuffer类的关键代码如下:</p>    <pre>  <code class="language-java">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型数组      }  //省略其他代码……  ……</code></pre>    <p>StringBuilder类的构造函数与StringBuffer类的构造函数实现方式相同,此处就不贴代码了。</p>    <p>下面来看看StringBuilder类的append方法和insert方法的代码,因StringBuilder和StringBuffer的方法实现基本上一致,不同的是StringBuffer类的方法前多了个synchronized关键字,即StringBuffer是线程安全的。所以接下来我们就只分析StringBuilder类的代码了。StringBuilder类的append方法,insert方法都是Override 父类AbstractStringBuilder的方法,所以我们直接来分析AbstractStringBuilder类的相关方法。</p>    <pre>  <code class="language-java">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);      }</code></pre>    <p>从上述代码分析得出,StringBuilder和StringBuffer的append方法“扩容”特性本质上是通过调用Arrays类的copyOf方法来实现的。接下来我们顺藤摸瓜,再分析下Arrays.copyOf(value, newCapacity)这个方法吧。代码如下:</p>    <pre>  <code class="language-java">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;//返回“扩容”后的数组变量      }</code></pre>    <p>其中,insert方法也是调用了expandCapacity方法来实现“扩容”特性的,此处就不在赘述了。</p>    <p>接下来,分析下delete(int start, int end)方法,代码如下:</p>    <pre>  <code class="language-java">public AbstractStringBuilder delete(int start, int end) {          if (start < 0)              throw new StringIndexOutOfBoundsException(start);          if (end > count)              end = count;          if (start > end)              throw new StringIndexOutOfBoundsException();          int len = end - start;          if (len > 0) {              //调用native方法arraycopy对value数组进行复制操作,然后重新赋值count变量达到“删除”特性              System.arraycopy(value, start+len, value, start, count-end);              count -= len;          }          return this;      }</code></pre>    <p>从源码可以看出delete方法的“删除”特性是调用native方法arraycopy对value数组进行复制操作,然后重新赋值count变量实现的</p>    <p>最后,来看下substring方法,源码如下 :</p>    <pre>  <code class="language-java">public String substring(int start, int end) {          if (start < 0)              throw new StringIndexOutOfBoundsException(start);          if (end > count)              throw new StringIndexOutOfBoundsException(end);          if (start > end)              throw new StringIndexOutOfBoundsException(end - start);          //根据start,end参数创建String对象并返回          return new String(value, start, end - start);      }</code></pre>    <h3><strong>四、总结:</strong></h3>    <p>1、String类型的字符串对象是不可变的,一旦String对象创建后,包含在这个对象中的字符系列是不可以改变的,直到这个对象被销毁。</p>    <p>2、StringBuilder和StringBuffer类型的字符串是可变的,不同的是StringBuffer类型的是线程安全的,而StringBuilder不是线程安全的</p>    <p>3、如果是多线程环境下涉及到共享变量的插入和删除操作,StringBuffer则是首选。如果是非多线程操作并且有大量的字符串拼接,插入,删除操作则StringBuilder是首选。毕竟String类是通过创建临时变量来实现字符串拼接的,耗内存还效率不高,怎么说StringBuilder是通过JNI方式实现终极操作的。</p>    <p>4、StringBuilder和StringBuffer的“可变”特性总结如下:</p>    <p>(1)append,insert,delete方法最根本上都是调用System.arraycopy()这个方法来达到目的</p>    <p>(2)substring(int, int)方法是通过重新new String(value, start, end - start)的方式来达到目的。因此,在执行substring操作时,StringBuilder和String基本上没什么区别。</p>    <p> </p>    <p>来自:http://www.jianshu.com/p/987d3727adc3</p>    <p> </p>