StringBuffer、StringBuilder源码分析

来源:互联网 发布:xmr显卡算力 知乎 编辑:程序博客网 时间:2024/05/15 23:45

环境:JDK8

类定义

StringBuffer:

  1. public final class StringBuffer
  2.    extends AbstractStringBuilder
  3.    implements java.io.Serializable, CharSequence

StringBuilder:

  1. public final class StringBuilder
  2.    extends AbstractStringBuilder
  3.    implements java.io.Serializable, CharSequence

可以看出两个类都是无法被继承的,因为都被final修饰。实现的接口一样,并且继承的抽象类也一样都是AbstractStringBuilder。


构造函数

StringBuffer:

  1. public StringBuffer() {
  2.        super(16);
  3. }

该构造函数传入整数参数16调用父类方法:

  1. AbstractStringBuilder(int capacity) {
  2.        value = new char[capacity];
  3.    }

可以看到,构造了一个容量为16的字符数组,由此可知,StringBuffer底层实现和String一样也是字符数组。

如果想指定容量构造StringBuffer,可用StringBuffer(int capacity)方法,这点看源码:

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

该方法也会调用父类的构造函数构造一个指定容量的字符数组。

这点StringBuilder和StringBuffer源码完全一样,就不贴代码了。


再来看另个构造函数StringBuffer(String str):

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

第一句上面提到了,不再说明。直接看append(String str)方法源码:

  1. @Override
  2.    public synchronized StringBuffer append(String str) {
  3.        toStringCache = null;
  4.        super.append(str);
  5.        return this;
  6.    }

可以看到StringBuffer的append方法是同步的,由于被synchronized修饰。

看toStringCache定义:

  1. private transient char[] toStringCache;

可以看到toStringCahe是被transient关键字修饰的字符数组。它主要用来缓存被toString方法返回的最后被修改过的值,StringBuffer每次被修改,它都会改变,因此它是瞬态的。

Append

接下来调用父类的append方法:

  1. public AbstractStringBuilder append(String str) {
  2.        if (str == null)
  3.            return appendNull();
  4.        int len = str.length();
  5.        ensureCapacityInternal(count + len);
  6.        str.getChars(0, len, value, count);
  7.        count += len;
  8.        return this;
  9.    }

首先判断字符串是否为空,是则添加一个空字符串并且容量加4,这点可看appendNull源码实现。接下来,字符串不为空,则扩容加上字符串长度,然后调用该字符串的getChars方法将源字符数组str复制到目标数组value,实现字符串的拼接,这点通过查看getChars方法源码可知。

getChars方法源码:

  1. public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
  2.        if (srcBegin < 0) {
  3.            throw new StringIndexOutOfBoundsException(srcBegin);
  4.        }
  5.        if (srcEnd > value.length) {
  6.            throw new StringIndexOutOfBoundsException(srcEnd);
  7.        }
  8.        if (srcBegin > srcEnd) {
  9.            throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
  10.        }
  11.        System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
  12.    }

接下来将字符的数量count加上字符串的长度len,最后返回当前对象的引用。


对比StringBuilder的append(String str)方法:

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

可以看到StringBuilder的append方法不是同步的,其他的语句和StringBuffer一样。


StringBuffer的append重载方法列表:

  1. public synchronized StringBuffer append(Object obj);
  2. public synchronized StringBuffer append(String str);
  3. public synchronized StringBuffer append(StringBuffer sb);
  4. synchronized StringBuffer append(AbstractStringBuilder asb);(这个方法是1.8开始添加的)。
  5. public synchronized StringBuffer append(CharSequence s);
  6. public synchronized StringBuffer append(char[] str);
  7. public synchronized StringBuffer append(char[] str, int offset, int len);
  8. public synchronized StringBuffer append(boolean b);
  9. public synchronized StringBuffer append(char c);
  10. public synchronized StringBuffer append(int i) ;
  11. public synchronized StringBuffer append(long lng);
  12. public synchronized StringBuffer append(float f);
  13. public synchronized StringBuffer append(double d);
  14. public synchronized StringBuffer append(CharSequence s, int start, int end);

以上重载方法源码在本类中的逻辑完全和append(String str)方法一样,都是调用父类的具体实现。

StringBuilder的append重载方法列表和StringBuffer的是同样的,但都是非同步的。


经过以上分析我们知道StringBuffer和StringBuilder的大多数方法都是调用父类的具体实现,只是StringBuffer的方法被synchronized修饰,当然不是全部都被synchronized修饰,而Stringbuilder的方法则一定没有被修饰。

因此可以得出结论:StringBuffer是同步的,可以用在多线程环境中,而StringBuilder则不能。


delete

接下来将分析StringBuffer和StringBuilder的共同调用父类AbstractStringBuilder的delete方法源码:

  1. public AbstractStringBuilder delete(int start, int end) {
  2.        if (start < 0)
  3.            throw new StringIndexOutOfBoundsException(start);
  4.        if (end > count)
  5.            end = count;
  6.        if (start > end)
  7.            throw new StringIndexOutOfBoundsException();
  8.        int len = end - start;
  9.        if (len > 0) {
  10.            System.arraycopy(value, start+len, value, start, count-end);
  11.            count -= len;
  12.        }
  13.        return this;
  14.    }

参数start,开始索引,包含。参数end,结束索引,不包含。

首先判断边界,可以看到end如果比字符的数量大,则end会被赋值为该字符数组的长度。

然后计算索引的差值是否大于0,及索引有效,若是则将字符数组value从start+len开始复制到start开始处并且复制长度为count-end,即删除从start开始到end为止的字符,并重新拼接拷贝,最后字符数量减去len。

delete方法的逻辑和append方法完全一样。


replace

接下来将分析StringBuffer和StringBuilder的共同调用父类AbstractStringBuilder的replace方法源码:

  1. public AbstractStringBuilder replace(int start, int end, String str) {
  2.        if (start < 0)
  3.            throw new StringIndexOutOfBoundsException(start);
  4.        if (start > count)
  5.            throw new StringIndexOutOfBoundsException("start > length()");
  6.        if (start > end)
  7.            throw new StringIndexOutOfBoundsException("start > end");
  8.        if (end > count)
  9.            end = count;
  10.        int len = str.length();
  11.        int newCount = count + len - (end - start);
  12.        ensureCapacityInternal(newCount);
  13.        System.arraycopy(value, end, value, start + len, count - end);
  14.        str.getChars(value, start);
  15.        count = newCount;
  16.        return this;
  17.    }

参数start,开始索引,包含。参数end,结束索引,不包含。

首先判断边界,计算字符串长度及字符数量,然后根据字符数量判断是否需要增加字符数组容量,这点可看ensureCapacityInternal方法源码,得知在该方法中会判断当前传递过去的字符数量newCount是否会比原本字符数组value长度大,若是则扩大容量,否则什么都不做,即替换后字符数组不会溢出:

  1. private void ensureCapacityInternal(int minimumCapacity) {
  2.        // overflow-conscious code
  3.        if (minimumCapacity - value.length > 0)
  4.            expandCapacity(minimumCapacity);
  5.    }

expandCapacity方法源码:

  1. void expandCapacity(int minimumCapacity) {
  2.        int newCapacity = value.length * 2 + 2;
  3.        if (newCapacity - minimumCapacity < 0)
  4.            newCapacity = minimumCapacity;
  5.        if (newCapacity < 0) {
  6.            if (minimumCapacity < 0) // overflow
  7.                throw new OutOfMemoryError();
  8.            newCapacity = Integer.MAX_VALUE;
  9.        }
  10.        value = Arrays.copyOf(value, newCapacity);
  11.    }

可以看到新的字符数组容量会扩大到原始容量的2倍再加上2。

最后调用Arrays的静态方法copyOf通过填充null或删除字符来扩大或减少字符数组的容量。


再回到replace(int start, int end, String str)方法,继续往下的代码逻辑就是字符数组value的拷贝,第一次拷贝从end开始,长度为count - end,拷贝到目标字符数组的索引从start + len开始。即拷贝不需要替换的字符到扩容或缩容后的字符数组的新位置。

第二次拷贝,将第一步拷贝后的字符数组作为参数调用str.getChars(value, start)方法,该方法内部是从0开始拷贝,长度为str字符串的长度,拷贝到目标字符数组的索引从start开始。即拷贝字符串str到value字符数组替换原本的字符。

最后将字符数量赋值为新的值,即替换后字符串的长度。


我们注意到以上两个方法都不是同步的,所以对于多线程环境下字符串的替换操作也推荐使用StringBuffer来实现。


insert

下面来看下insert方法的源码:

  1. public AbstractStringBuilder insert(int offset, String str) {
  2.        if ((offset < 0) || (offset > length()))
  3.            throw new StringIndexOutOfBoundsException(offset);
  4.        if (str == null)
  5.            str = "null";
  6.        int len = str.length();
  7.        ensureCapacityInternal(count + len);
  8.        System.arraycopy(value, offset, value, offset + len, count - offset);
  9.        str.getChars(value, offset);
  10.        count += len;
  11.        return this;
  12.    }

可以看到首先开始是边界判断,从第6行开始往后的逻辑就和replate方法一样了,都要进行两次字符数组的拷贝。相关方法源码上面已讲解过,不再深入。


indexOf

indexOf方法实际是调用String类的静态方法tatic int indexOf(char[] source, int sourceOffset, int sourceCount,
       char[] target, int targetOffset, int targetCount, int fromIndex),具体分析看这篇

http://blog.csdn.net/u011726984/article/details/51326697#t6 

indexOf(String)方法返回的是子串首次出现在字符串中的位置索引,而lastIndexOf(String)方法返回的是子串最后一次出现在字符串中的位置索引。

  1. StringBuffer stringBuffer = new StringBuffer("abcdc");
  2. int i = stringBuffer.indexOf("c");
  3. int j = stringBuffer.lastIndexOf("c");
  4. System.out.print("i="+i + "\tj=" + j);

结果为:

  1. i=2j=4

若搜索的字符串为“cd”则都返回2。


indexOf(String, int)返回的是字串从指定位置开始搜索首次出现在字符串中的位置索引,而lastIndexOf(String, int)返回的是字串从指定位置为结束位置开始搜索最后一次出现在字符串中的位置索引。

  1. StringBuffer stringBuffer = new StringBuffer("abcdc");
  2. int i = stringBuffer.indexOf("cd", 3);
  3. int j = stringBuffer.lastIndexOf("cd", 3);
  4. System.out.print("i="+i + "\tj=" + j);
结果为:
  1. i=-1j=2

这里第3句,应该从索引3开始查找,j值应该返回-1才对,可是结果却返回2。

将第2行参数值改为1,即从索引1开始搜索,结果为:

  1. i=2j=2

将第2、3行参数都改为1,结果:

  1. i=2j=-1

以上结果本人有点费解,查看indexOf和lastIndexOf的源码说明,第二个参数应该是开始搜索的索引的位置,可是结果却和源码说明矛盾。

subSequence&substring

subSequence方法返回调用的是substring:

  1. public CharSequence subSequence(int start, int end) {
  2.        return substring(start, end);
  3.    }

关于substring方法源码分析查看http://blog.csdn.net/u011726984/article/details/51326697#t5 


toString

先看StringBuffer的toString方法源码:

  1. public synchronized String toString() {
  2. if (toStringCache == null) {
  3. toStringCache = Arrays.copyOfRange(value, 0, count);
  4. }
  5. return new String(toStringCache, true);
  6. }

对比Stringbuilder的toString方法源码:

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

通过对比可以看出StringBuffer的toString方法返回的是“共享数组”,而StringBuilder返回的是普通数组。

具体原因查看此文http://blog.csdn.net/u011726984/article/details/51326697 的concat方法源码的讲解,其中有提到。


如有疏漏请指出,谢谢!

0 0
原创粉丝点击