zoukankan      html  css  js  c++  java
  • 小学徒成长系列—StringBuilder & StringBuffer关键源码解析

    在前面的博文《小学徒成长系列—String关键源码解析》和《小学徒进阶系列—JVM对String的处理》中,我们讲到了关于String的常用方法以及JVM对字符串常量String的处理。

    但是在Java中,关于字符串操作的类还有两个,它们分别是StringBuilder和StringBuffer。我们先来就讲解一下String类和StringBuilder、StringBuffer的联系吧。

    String、StringBuilder、StringBuffer的异同点

    结合之前写的博文,我们对这三个常用的类的异同点进行分析:

    异:

    1>String的对象是不可变的;而StringBuilder和StringBuffer是可变的。

    2>StringBuilder不是线程安全的;而StringBuffer是线程安全的

    3>String中的offset,value,count都是被final修饰的不可修改的;而StringBuffer和StringBuilder中的value,count都是继承自AbstractStringBuilder类的,没有被final修饰,说明他们在运行期间是可修改的,而且没有offset变量。

    同:

    三个类都是被final修饰的,是不可被继承的。

    StringBuilder和StringBuffer的构造方法

     其实StringBuilder和StringBuffer的构造方法类型是一样的,里面都是通过调用父类的构造方法进行实现的,在这里,我主要以StringBuilder为例子讲解,StringBuffer就不重复累赘的讲啦。

    1>构建一个初始容量为16的默认的字符串构建

    1 public StringBuilder() {
    2     super(16);
    3 }

    从构造方法中我们看到,构造方法中调用的是父类AbstractStringBuilder中的构造方法,我们来看看,父类中的构造方法:

    1 /**
    2  * 构造一个不带任何字符的字符串生成器,其初始容量由 capacity 参数指定。
    3  * @params capacity 数组初始化容量
    4  */
    5 AbstractStringBuilder(int capacity) {
    6     value = new char[capacity];
    7 }

    这个构造方法说明的是,创建一个初始容量由 capacity 参数指定的字符数组,而子类中传过来的是16,所以创建的就是初始容量为16的字符数组

    2>构造一个不带任何字符的字符串生成器,其初始容量由 capacity 参数指定。

    1 public StringBuilder(int capacity) {
    2     super(capacity);
    3 }

    这个构造方法调用的跟上面1>的构造方法是同一个的,只是这里子类中的初始化容量由用户决定。

    3>构造一个字符串生成器,并初始化为指定的字符串内容。该字符串生成器的初始容量为 16 加上字符串参数的长度。

    1 public StringBuilder(String str) {
    2     super(str.length() + 16);
    3     append(str);
    4 }

    这个构造方法首先调用和1>一样的父类构造方法,然后再调用本类中的append()方法将字符串str拼接到本对象已有的字符串之后。

    4>构造一个字符串生成器,包含与指定的 CharSequence 相同的字符。该字符串生成器的初始容量为 16 加上 CharSequence 参数的长度。

    1 public StringBuilder(CharSequence seq) {
    2     this(seq.length() + 16);
    3     append(seq);
    4 }

    嗯,这个构造方法,大家一看就知道跟上面的差不多啦,我就不介绍啦。

    StringBuilder常用的方法

    在StringBuilder中,很多方法最终都是进行一定的逻辑处理,然后通过调用父类AbstractStringBuilder中的方法进行实现的。

     1>append(String str)

      从下面的代码中我们可以看到,他是直接调用父类的append方法进行实现的。

    1 public StringBuilder append(String str) {
    2     super.append(str);
    3     return this;
    4 }

      下面我们再看下父类AbstractStringBuilder中的append方法是怎么写的

     1 public AbstractStringBuilder append(String str) {
     2     //注意,当str的值为nul时,将会在当前字符串对象后面添加上Null字符串
     3     if (str == null) str = "null";
     4     //获取需要添加的字符串的长度
     5     int len = str.length();
     6     //判断添加后的字符串对象是否超过容量,若是,扩容
     7     ensureCapacityInternal(count + len);
     8     //将str中的字符串复制到value数组中
     9     str.getChars(0, len, value, count);
    10     //更新当前字符串对象的字符串长度
    11     count += len;
    12     return this;
    13 }

      2> ensureCapacityInternal

      下面我们看下,他每次拼接字符串的时候,是怎样进行扩容的:

     1  /**
     2   * This method has the same contract as ensureCapacity, but is
     3   * never synchronized.
     4   */
     5 private void ensureCapacityInternal(int minimumCapacity) {
     6     // overflow-conscious code
     7     // 如果需要扩展到的容量比当前字符数组长度要大
     8     // 那么就正常扩容
     9     if (minimumCapacity - value.length > 0)
    10         expandCapacity(minimumCapacity);
    11 }
    12 
    13 /**
    14  * This implements the expansion semantics of ensureCapacity with no
    15  * size check or synchronization.
    16  */
    17 void expandCapacity(int minimumCapacity) {
    18     // 初始化新的容量大小为当前字符串长度的2倍加2
    19     int newCapacity = value.length * 2 + 2;
    20     // 如果新容量大小比传进来的最小容量还要小
    21     // 就是用最小的容量为新数组的容量
    22     if (newCapacity - minimumCapacity < 0)
    23         newCapacity = minimumCapacity;
    24     // 如果新的容量或者最小容量小于0
    25     // 抛异常并且讲新容量设置成Integer最能存储的最大值
    26     if (newCapacity < 0) {
    27         if (minimumCapacity < 0) // overflow
    28             throw new OutOfMemoryError();
    29         newCapacity = Integer.MAX_VALUE;
    30     }
    31     // 创建容量大小为newCapacity的新数组
    32     value = Arrays.copyOf(value, newCapacity);
    33 }

      3>append(StringBuffer sb) 

      从这里我们可以看到,它又是调用父类的方法进行拼接的。

    1 public StringBuilder append(StringBuffer sb) {
    2     super.append(sb);
    3     return this;
    4 }

      继续看父类中的拼接方法:

     1  // Documentation in subclasses because of synchro difference
     2 public AbstractStringBuilder append(StringBuffer sb) {
     3     // 如果sb的值为null,这里就会为字符串添加上字符串“null”
     4     if (sb == null)
     5         return append("null");
     6     // 获取需要拼接过来的字符串的长度
     7     int len = sb.length();
     8     // 扩容当前兑现搞定字符数组容量
     9     ensureCapacityInternal(count + len);
    10     // 进行字符串的拼接
    11     sb.getChars(0, len, value, count);
    12     // 更新当前字符串对象的长度变量
    13     count += len;
    14     return this;
    15 }

      4>public StringBuilder delete(int start, int end) 

        删除从start开始到end结束的字符(包括start但不包括end)

    1 public StringBuilder delete(int start, int end) {
    2     super.delete(start, end);
    3     return this;
    4 }

        是的,又是调用父类进行操作的。

     1 /**
     2  * Removes the characters in a substring of this sequence.
     3  * The substring begins at the specified {@code start} and extends to
     4  * the character at index {@code end - 1} or to the end of the
     5  * sequence if no such character exists. If
     6  * {@code start} is equal to {@code end}, no changes are made.
     7  *
     8  * @param      start  The beginning index, inclusive.
     9  * @param      end    The ending index, exclusive.
    10  * @return     This object.
    11  * @throws     StringIndexOutOfBoundsException  if {@code start}
    12  *             is negative, greater than {@code length()}, or
    13  *             greater than {@code end}.
    14  */
    15 public AbstractStringBuilder delete(int start, int end) {
    16     // 健壮性的检查
    17     if (start < 0)
    18         throw new StringIndexOutOfBoundsException(start);
    19     if (end > count)
    20         end = count;
    21     if (start > end)
    22         throw new StringIndexOutOfBoundsException();
    23     // 需要删除的长度
    24     int len = end - start;
    25     if (len > 0) {
    26         // 进行复制,将被删除的元素后面的复制到前面去
    27         System.arraycopy(value, start+len, value, start, count-end);
    28         // 更新字符串长度
    29         count -= len;
    30     }
    31     return this;
    32 }

    其实看了那么多,我们也很容易发现,不管是String类还是现在博文中的StringBuilder和StringBuffer,底层实现都用到了Arrays.copyOfRange(original, from, to);和System.arraycopy(src, srcPos, dest, destPos, length);这两个方法实现的。

      在看完上面那段源代码之后,我突然想到了一个问题,就是如果需要剩下的字符个数少于需要被覆盖的字符个数时怎么办,看下面的代码:

     1 import java.util.Arrays;
     2 
     3 public class StringBuilderTest {
     4     public static void main(String[] args) {
     5         char[] src = {'a', 'b', 'c', 'd', 'e', 'f', 'g'};
     6         int start = 4;
     7         int end = 5;
     8         int len = end - start;
     9         if (len > 0) {
    10             //进行复制,将被删除
    11             System.arraycopy(src, start+len, src, start, src.length-end);
    12         }
    13         System.out.println(src);
    14 
    15         StringBuilder stringBuilder = new StringBuilder("abcdefg");
    16         stringBuilder.delete(4, 5);
    17         System.out.println(stringBuilder);
    18     }
    19 }

    结果输出了:

    奇怪,为什么StringBuilder可以输出abcdefg而我的会多了一个g呢?原因是在StringBuilder中的toString方法中重新创建了一个有效数字为count的,也就是说值为abcdefg的字符串对象,如下代码:

    1 public String toString() {
    2     // Create a copy, don't share the array
    3     return new String(value, 0, count);
    4     }

      5>public StringBuilder replace(int start, int end, String str)

     关于这个方法,因为是直接调用父类中的方法进行实现的,所以我们继续直接看父类中的方法吧:

     1 /**
     2  * Replaces the characters in a substring of this sequence
     3  * with characters in the specified <code>String</code>. The substring
     4  * begins at the specified <code>start</code> and extends to the character
     5  * at index <code>end - 1</code> or to the end of the
     6  * sequence if no such character exists. First the
     7  * characters in the substring are removed and then the specified
     8  * <code>String</code> is inserted at <code>start</code>. (This
     9  * sequence will be lengthened to accommodate the
    10  * specified String if necessary.)
    11  *
    12  * @param      start    The beginning index, inclusive.
    13  * @param      end      The ending index, exclusive.
    14  * @param      str   String that will replace previous contents.
    15  * @return     This object.
    16  * @throws     StringIndexOutOfBoundsException  if <code>start</code>
    17  *             is negative, greater than <code>length()</code>, or
    18  *             greater than <code>end</code>.
    19  */
    20 public AbstractStringBuilder replace(int start, int end, String str) {
    21     // 健壮性的检查
    22     if (start < 0)
    23         throw new StringIndexOutOfBoundsException(start);
    24     if (start > count)
    25         throw new StringIndexOutOfBoundsException("start > length()");
    26     if (start > end)
    27         throw new StringIndexOutOfBoundsException("start > end");
    28 
    29     if (end > count)
    30         end = count;
    31 
    32     // 获取需要添加的字符串的长度
    33     int len = str.length();
    34     // 计算新字符串的长度
    35     int newCount = count + len - (end - start);
    36     // 对当前对象的数组容量进行扩容
    37     ensureCapacityInternal(newCount);
    38     // 进行数组的中的元素移位,从而空出足够的空间来容纳需要添加的字符串
    39     System.arraycopy(value, end, value, start + len, count - end);
    40     // 将str复制到value中
    41     str.getChars(value, start);
    42     // 更新字符串长度
    43     count = newCount;
    44     return this
    45 }

      6>public StringBuilder insert(int offset, String str)

      在offset位置插入字符串str,他的实现也是通过父类进行实现的,继续看父类中的相应方法:

     1 /**
     2  * Inserts the string into this character sequence.
     3  * <p>
     4  * The characters of the {@code String} argument are inserted, in
     5  * order, into this sequence at the indicated offset, moving up any
     6  * characters originally above that position and increasing the length
     7  * of this sequence by the length of the argument. If
     8  * {@code str} is {@code null}, then the four characters
     9  * {@code "null"} are inserted into this sequence.
    10  * <p>
    11  * The character at index <i>k</i> in the new character sequence is
    12  * equal to:
    13  * <ul>
    14  * <li>the character at index <i>k</i> in the old character sequence, if
    15  * <i>k</i> is less than {@code offset}
    16  * <li>the character at index <i>k</i>{@code -offset} in the
    17  * argument {@code str}, if <i>k</i> is not less than
    18  * {@code offset} but is less than {@code offset+str.length()}
    19  * <li>the character at index <i>k</i>{@code -str.length()} in the
    20  * old character sequence, if <i>k</i> is not less than
    21  * {@code offset+str.length()}
    22  * </ul><p>
    23  * The {@code offset} argument must be greater than or equal to
    24  * {@code 0}, and less than or equal to the {@linkplain #length() length}
    25  * of this sequence.
    26  *
    27  * @param      offset   the offset.
    28  * @param      str      a string.
    29  * @return     a reference to this object.
    30  * @throws     StringIndexOutOfBoundsException  if the offset is invalid.
    31  */
    32 public AbstractStringBuilder insert(int offset, String str) {
    33     if ((offset < 0) || (offset > length()))
    34         throw new StringIndexOutOfBoundsException(offset);
    35     if (str == null)
    36         str = "null";
    37     int len = str.length();
    38     ensureCapacityInternal(count + len);
    39     // 将字符串后移为插入的字符串留充足的空间
    40     System.arraycopy(value, offset, value, offset + len, count - offset);
    41     // 将str复制到value数组中  
    42     str.getChars(value, offset);
    43     // 更新当前对象中记录的长度
    44     count += len;
    45     return this;
    46 }

      7>indexOf(String str)

      其实这个的实现主要是借助了String对象的indexOf方法来实现的,具体可以参考博文:http://www.cnblogs.com/xiaoxuetu/archive/2013/06/05/3118229.html 这里就不详细进行讲解了:

    1 /**
    2  * @throws NullPointerException {@inheritDoc}
    3  */
    4 public int indexOf(String str) {
    5     return indexOf(str, 0);
    6 }

    调用了同一个类中的indexOf方法:

    1 /**
    2  * @throws NullPointerException {@inheritDoc}
    3  */
    4 public int indexOf(String str, int fromIndex) {
    5     //调用了String类中的静态方法indexOf
    6     return String.indexOf(value, 0, count,
    7                           str.toCharArray(), 0, str.length(), fromIndex);
    8 }

    String.indexOf()方法是默认权限的,也就是只有与他同包的情况下才能够进行访问这个方法。

      8> lastIndexOf()

      lastIndexOf()方法跟indexOf()差不多,调用了String.lastIndexOf()方法进行实现,再次不重复说明。

      9> public StringBuilder reverse()

         我们经常进行字符串的逆转,面试的时候也有经常问到,那么实际上在jdk中式怎么完成这些操作的呢?

         首先我们看下StringBuilder中的reverse方法():

    1 public StringBuilder reverse() {
    2     //调用父类的reverse方法
    3     super.reverse();
    4     return this;
    5 }

        一般情况下,如果让我们来进行逆转,会怎么写呢?我想很多人都会像下面那样子写吧:

    1 public String reverse(char[] value){
    2     //折半,从中间开始置换
    3     for (int i = (value.length - 1) >> 1; i >= 0; i--){
    4        char temp = value[i];
    5        value[i] = value[value.length - 1 - i];
    6        value[value.length - 1 - i] = temp;
    7     }
    8     return new String(value);
    9 }

        确实很简单,但是一个完整的 Unicode 字符叫代码点CodePoint,而一个 Java char 叫 代码单元 code unit。如果String 对象以UTF-16保存 Unicode 字符,需要用2个字符表示一个超大字符集的汉字,这这种表示方式称之为 Surrogate,第一个字符叫 Surrogate High,第二个就是 Surrogate Low。所在在JDK中也加入了判断一个char是否是Surrogate区的字符:

     1 /**
     2  * Causes this character sequence to be replaced by the reverse of
     3  * the sequence. If there are any surrogate pairs included in the
     4  * sequence, these are treated as single characters for the
     5  * reverse operation. Thus, the order of the high-low surrogates
     6  * is never reversed.
     7  *
     8  * Let <i>n</i> be the character length of this character sequence
     9  * (not the length in <code>char</code> values) just prior to
    10  * execution of the <code>reverse</code> method. Then the
    11  * character at index <i>k</i> in the new character sequence is
    12  * equal to the character at index <i>n-k-1</i> in the old
    13  * character sequence.
    14  *
    15  * <p>Note that the reverse operation may result in producing
    16  * surrogate pairs that were unpaired low-surrogates and
    17  * high-surrogates before the operation. For example, reversing
    18  * "&#92;uDC00&#92;uD800" produces "&#92;uD800&#92;uDC00" which is
    19  * a valid surrogate pair.
    20  *
    21  * @return  a reference to this object.
    22  */
    23 public AbstractStringBuilder reverse() {
    24     // 默认没有存储到Surrogate区的字符
    25     boolean hasSurrogate = false;
    26     int n = count - 1;
    27     // 折半,遍历并且首尾相应位置置换
    28     for (int j = (n-1) >> 1; j >= 0; --j) {
    29         char temp = value[j];
    30         char temp2 = value[n - j];
    31         if (!hasSurrogate) {
    32             // 判断一个char是否是Surrogate区的字符
    33             hasSurrogate = (temp >= Character.MIN_SURROGATE && temp <= Character.MAX_SURROGATE)
    34                 || (temp2 >= Character.MIN_SURROGATE && temp2 <= Character.MAX_SURROGATE);
    35         }
    36         // 首尾值置换
    37         value[j] = temp2;
    38         value[n - j] = temp;
    39     }
    40 
    41     // 如果含有Surrogate区的字符
    42     if (hasSurrogate) {
    43         // Reverse back all valid surrogate pairs
    44         for (int i = 0; i < count - 1; i++) {
    45             char c2 = value[i];
    46             if (Character.isLowSurrogate(c2)) {
    47                 char c1 = value[i + 1];
    48                 if (Character.isHighSurrogate(c1)) {
    49                     //下面这行代码相当于
    50                     //value[i]=c1; i=i+1;
    51                     value[i++] = c1;
    52                     value[i] = c2;
    53                 }
    54             }
    55         }
    56     }
    57     return this;
    58 }

     StringBuffer的常用方法

     前面我们知道StringBuffer相当于StringBuilder来说是线程安全的,所以再StringBuffer中,所有的方法都加了同步synchronized,例如append(String str)方法:

    public synchronized StringBuffer append(String str) {
        super.append(str);
        return this;
    }

    具体内部实现就不详细说明啦,跟StringBuilder是一样的,大部分都是调用AbstractStringBuilder进行实现的。

  • 相关阅读:
    Hadoop书籍介绍
    WeakReference,SoftReference 和 PhatomReference 浅析
    如何在Java中定义常量(Constant)
    也谈谈Java的垃圾收集(garbage collection)
    csdn的新家
    安装和使用Oracle Instant Client 和 SQLPlus
    Perl中的grep和map
    用Devel::NYTProf 优化perl脚本性能
    DataBase
    Linux下配置listener和tns
  • 原文地址:https://www.cnblogs.com/xiaoxuetu/p/3129623.html
Copyright © 2011-2022 走看看