StringBuilder与StringBuffer源码分析

StringBuilder与StringBuffer,估计面试被问这两个的问题应该很常见了。但只答出线程安不安全那是远远不够的。

这个两个东西的出现大多人应该都知道,因为String不可变,如果想强行可变,那么会导致一直创建新的String对象。StringBuilder,StringBuffer这两个就可以动态添加字符串。那么它们是怎么实现的呢,下面就从源码来进一步认识它们。

StringBuilder:

 因为String不可变,Java 引入了可变字符串变量 StringBuilder 类,但是它不是线程安全的,只用在单线程场景下。

public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequencee

通过源码可以看到,StringBuilder继承自AbstractStringBuilder。

abstract class AbstractStringBuilder implements Appendable, CharSequence {
    /**
     * The value is used for character storage.
     */
    char[] value;

    /**
     * The count is the number of characters used.
     */
    int count;

在AbstractStringBuilder可以看到这个两个属性,这是实现StringBuilder中最重要的属性。

value 该数组用于存储字符串值。

count 表示该字符串对象中已使用的字符数。

构造函数:

public StringBuilder() {                //默认容量大小为16
    super(16);
}

public StringBuilder(int capacity) {    //传入一个int值,指定容量的大小
    super(capacity);
}

public StringBuilder(String str) {     //传入String类型,容量为传入的字符串长度加上16
    super(str.length() + 16);
    append(str);
}

public StringBuilder(CharSequence seq) {    //传入CharSequence,传入参数为 CharSequence 类型时也跟上面一样做相同处理
   this(seq.length() + 16);
   append(seq);
}

主要方法:

append方法:

有多个append方法,都只是传入的参数不同而已,下面挑几个典型的深入看看,其他都是类似的处理。

public StringBuilder append(String str) {
    super.append(str);            //调用父类的append方法
    return this;
}

public AbstractStringBuilder append(String str) {
    if (str == null)            //如果传入的是null,则直接调用appendNull方法
        return appendNull();
    int len = str.length();
    ensureCapacityInternal(count + len);    //根据判断是否需要扩容进行扩容操作
    str.getChars(0, len, value, count);     //合并数据
    count += len;                           //增加已使用的字符数
    return this;
}

源码我已加上注释,继续跟进去,下面看ensureCapacityInternal方法

private void ensureCapacityInternal(int minimumCapacity) {
   // overflow-conscious code
   if (minimumCapacity - value.length > 0) {  //判断所需要的长度是否大于当前长度,是则扩容
       value = Arrays.copyOf(value,           //扩容后将原来的数组复制过来
       newCapacity(minimumCapacity));        //newCapacity  扩容的方法
    }
}

private int newCapacity(int minCapacity) {
   // overflow-conscious code
   int newCapacity = (value.length << 1) + 2;    //扩大一倍再加上2
   if (newCapacity - minCapacity < 0) {          //如果还是不够大则直接等于需要的容量大小
       newCapacity = minCapacity;
   }                                             //如果为负数,则设置为MAX_ARRAY_SIZE
   return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
       ? hugeCapacity(minCapacity)
       : newCapacity;
}

跟到这里,我们可以知道,newCapacity方法用于确定新容量大小,将现有容量大小扩大一倍再加上2,如果还是不够大则直接等于需要的容量大小。如果新容量大小为负则容量设置为MAX_ARRAY_SIZE,它的大小等于Integer.MAX_VALUE - 8

继续看getChars方法,前面做了一些基本的越界判断,最后其实里面就是使用arraycopy方法进行数组复制数据。

public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
    if (srcBegin < 0) {
       throw new StringIndexOutOfBoundsException(srcBegin);
    }
    if (srcEnd > value.length) {
       throw new StringIndexOutOfBoundsException(srcEnd);
    }
    if (srcBegin > srcEnd) {
       throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
    }
    System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}

在这里也顺便也介绍一下arraycopy这个方法吧

public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)

Object src : 原数组

int srcPos : 从元数据的起始位置开始

Object dest : 目标数组

int destPos : 目标数组的开始起始位置

int length  : 要copy的数组的长度

delete方法:

该方法用于将指定范围的字符删掉,delete方法比较简单,看源码

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

public AbstractStringBuilder delete(int start, int end) {
    if (start < 0)            //判断是否越界
        throw new StringIndexOutOfBoundsException(start);
    if (end > count)        //end 不能大于已使用字符数 count,大于的话则令其等于 count
        end = count;
    if (start > end)        //判断是否越界
        throw new StringIndexOutOfBoundsException();
    int len = end - start;
    if (len > 0) {         //判断完后使用arraycopy方法,把 end 后面的字符串复制到 start 位置,即相当于将中间的字符删掉
        System.arraycopy(value, start+len, value, start, count-end);
        count -= len;    //最后修改count值,减去删除的长度。
    }
    return this;
}

replace方法:

public AbstractStringBuilder replace(int start, int end, String str) {
    if (start < 0)
        throw new StringIndexOutOfBoundsException(start);
    if (start > count)
        throw new StringIndexOutOfBoundsException("start > length()");
    if (start > end)
        throw new StringIndexOutOfBoundsException("start > end");

    if (end > count)            //end 不能大于已使用字符数 count,大于的话则令其等于 count
        end = count;
    int len = str.length();
    int newCount = count + len - (end - start);    //计算出新count
    ensureCapacityInternal(newCount);              //判断是否需要扩容

    System.arraycopy(value, end, value, start + len, count - end);
    str.getChars(value, start);                    
    count = newCount;                               //更新count
    return this;
}
void getChars(char dst[], int dstBegin) {
      System.arraycopy(value, 0, dst, dstBegin, value.length);
}

该方法用于将指定范围的字符替换成指定字符串。ensureCapacityInternal方法跟System.arraycopy可参照前面。

deleteCharAt方法:

此方法可以删除指定索引位置的元素。

public AbstractStringBuilder deleteCharAt(int index) {
    if ((index < 0) || (index >= count))                        //判断是否越界
        throw new StringIndexOutOfBoundsException(index);
    System.arraycopy(value, index+1, value, index, count-index-1);    //数据操作
    count--;                                                    //因为是删除一个,所以count-1
    return this;
}

类似的,也是操作arraycopy实现。

StringBuilder主要方法就介绍到这里。

StringBuffer:

很多人都知道StringBuffer是线程安全的,那它是怎么实现的?

其实就是jdk提供的synchronized锁,它在对操作数据的方法加了同步锁。

StringBuffer跟Stringbuilder的方法实现差不多,只是多了一个锁。所以这里就提供append方法跟delete方法,看源码:

public synchronized StringBuffer append(String str) {
    toStringCache = null;
    super.append(str);
    return this;
}
public synchronized StringBuffer delete(int start, int end) {
    toStringCache = null;
    super.delete(start, end);
    return this;
}

可以看到StringBuffer的源码中,主要是通过在必要的方法上加 synchronized 来实现线程安全。

总结:

1、其实StringBuffer 和 StringBuilder 其实实现逻辑几乎都一样,并且抽象到 AbstractStringBuilder 抽象类中来实现,只是 StringBuffer 将一些必要的方法进行同步处理了。

2、使用上看程序是否需要关注线程安全,如果是单线程的话那就直接可以用StringBuilder,因为没有锁的抢占,效率快。如果需要考虑线程安全问题,那么就要使用StringBuffer。

3、System.arraycopy主要是这个方法在操作的数据的增减,掌握它是怎么实现复制操作数据的。

4、自己主动去跟一遍源码才是最好的。

 

最后想说,现在许多程序员做了两三年,基础方面还是很薄弱,认为一直学新框架提升自己,其实这不完全对的,这样就像是虚胖子,还是应该得把基础打好,内功练好。大多人看一个程序员的技术怎样,肯定是先问基础。

 

 

你可能感兴趣的:(JDK源码)