StringBuffer源码分析

来源:互联网 发布:mp3剪切合并大师 mac 编辑:程序博客网 时间:2024/06/10 18:02

StringBuffer在进行字符串处理时,不生成新的对象,在内存使用上要优于String类,所以在实际使用时,如果经常需要对一个字符串进行修改,例如追加、插入、删除等操作,使用StringBuffer要更加适合一些。对于StringBuffer对象的每次修改都会改变对象自身,这点是和String类最大的区别。

public final class StringBuffer    extends AbstractStringBuilder    implements java.io.Serializable, CharSequence{……}

1 初始化

这里写图片描述
对应的源码为:

    public StringBuffer() {        super(16);    }    public StringBuffer(int capacity) {        super(capacity);    }    public StringBuffer(String str) {        super(str.length() + 16);        append(str);    }    public StringBuffer(CharSequence seq) {        this(seq.length() + 16);        append(seq);    }

StringBuffer连初始化都要用到append,我们接下来看看append。

2 append

查看StringBuffer中多种append方法的源码,我们发现,很多都是直接调用超类的append方法,所以我们来看看AbstractStringBuilder中几个典型的append是怎么实现的。

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;    …………………………    public AbstractStringBuilder append(String str) {        if (str == null) str = "null";        int len = str.length();        ensureCapacityInternal(count + len);        //void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin)        str.getChars(0, len, value, count);        count += len;        return this;    }    private void ensureCapacityInternal(int minimumCapacity) {        // overflow-conscious code        if (minimumCapacity - value.length > 0)            expandCapacity(minimumCapacity);    }        /**     * This implements the expansion semantics of ensureCapacity with no     * size check or synchronization.     */    void expandCapacity(int minimumCapacity) {        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.copyOf(value, newCapacity);    }
//char[] java.util.Arrays.copyOf(char[] original, int newLength)    public static char[] copyOf(char[] original, int newLength) {        char[] copy = new char[newLength];        System.arraycopy(original, 0, copy, 0,                         Math.min(original.length, newLength));        return copy;    }

看完超类的代码相信就明白了,原来StringBuffer也不过是用char[] value在存储数据。如果用new StringBuffer() 新建,默认给该数组分配16char的空间。对StringBuffer进行append时会先用ensureCapacityInternal判断是否有足够的空间,如果空间不够,就尝试将容量扩大到原来的2倍(+2),判断够不够;如果够,就以新的容量新建一个数组;如果不够,就以需要的容量新建数组。然后把原数组里的数据复制到新数组中。

关于append其他类型的字符串(或字符数组),跟上面的流程一样,这里不在赘述。有点不一样的就是append其他数据类型的话,该方法会将其转化为标准的字符串类型追加到原StringBuffer后面。

分析到这里,其实StringBuffer的本质已经被展示出来了,所以insert、delete等操作其实也就是对于字符数组在进行操作。后

3 StringBuffer与String

网上说StringBuffer比String的优越性都说的比较笼统,分析了上面StringBuffer的源码之后,也没有感觉特别高效率。而且String到底是怎么进行“+”连接的也还没有弄清楚。

这里,引用另一篇博客的内容可以很好地说明这个问题。(http://blog.csdn.net/shi1122/article/details/8053680)

我们通过一个简单的程序来看其执行的流程:

public class Buffer {       public static void main(String[] args) {              String s1 = "aaaaa";              String s2 = "bbbbb";              String r = null;              int i = 3694;              r = s1 + i + s2;               for(int j=0;i<10;j++){                  r+="23124";              }       }  }  

使用命令javap -c Buffer查看其字节码实现:
这里写图片描述

将清单1和清单2对应起来看,清单2的字节码中ldc指令即从常量池中加载“aaaaa”字符串到栈顶,istore_1将“aaaaa”存到变量1中,后面的一样,sipush是将一个短整型常量值(-32768~32767)推送至栈顶,这里是常量“3694”,更多的Java指令集请查看另一篇文章“Java指令集”。

让我们直接看到13,13~17是new了一个StringBuffer对象并调用其初始化方法,20~21则是先通过aload_1将变量1压到栈顶,前面说过变量1放的就是字符串常量“aaaaa”,接着通过指令invokevirtual调用StringBuffer的append方法将“aaaaa”拼接起来,后续的24~30同理。最后在33调用StringBuffer的toString函数获得String结果并通过astore存到变量3中。

看到这里可能有人会说,“既然JVM内部采用了StringBuffer来连接字符串了,那么我们自己就不用用StringBuffer,直接用”+“就行了吧!“。是么?当然不是了。俗话说”存在既有它的理由”,让我们继续看后面的循环对应的字节码。

37~42都是进入for循环前的一些准备工作,37,38是将j置为1。44这里通过if_icmpge将j与10进行比较,如果j大于10则直接跳转到73,也即return语句退出函数;否则进入循环,也即47~66的字节码。这里我们只需看47到51就知道为什么我们要在代码中自己使用StringBuffer来处理字符串的连接了,因为每次执行“+”操作时jvm都要new一个StringBuffer对象来处理字符串的连接,这在涉及很多的字符串连接操作时开销会很大。

还有一点上面没有提到的是:当用StringBuffer进行append而容量足够时的开销是非常小的。一个StringBuffer对象不停append,用大约每次翻倍的方式增加容量从而从总体上减少了重新申请内存的次数。不像String “+”每次新建一个StringBuffer,每次申请内存,这样的开销非常大。

4 StringBuffer与StringBuilder

StringBuffer 与 StringBuilder 中的方法和功能完全是等价的,只是StringBuffer 中的方法大都采用了 synchronized 关键字进行修饰,因此是线程安全的,而 StringBuilder 没有这个修饰,可以被认为是线程不安全的。 下面引用一下JDK官方文档上面的表述。

StringBuffer:线程安全的可变字符序列。一个类似于 String 的字符串缓冲区,但不能修改。虽然在任意时间点上它都包含某种特定的字符序列,但通过某些方法调用可以改变该序列的长度和内容。

可将字符串缓冲区安全地用于多个线程。可以在必要时对这些方法进行同步,因此任意特定实例上的所有操作就好像是以串行顺序发生的,该顺序与所涉及的每个线程进行的方法调用顺序一致。

StringBuffer 上的主要操作是 append 和 insert 方法,可重载这些方法,以接受任意类型的数据。每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符追加或插入到字符串缓冲区中。append 方法始终将这些字符添加到缓冲区的末端;而 insert 方法则在指定的点添加字符。

例如,如果 z 引用一个当前内容为 “start” 的字符串缓冲区对象,则此方法调用 z.append(“le”) 会使字符串缓冲区包含 “startle”,而 z.insert(4, “le”) 将更改字符串缓冲区,使之包含 “starlet”。

通常,如果 sb 引用 StringBuilder 的一个实例,则 sb.append(x) 和 sb.insert(sb.length(), x) 具有相同的效果。

当发生与源序列有关的操作(如源序列中的追加或插入操作)时,该类只在执行此操作的字符串缓冲区上而不是在源上实现同步。

每个字符串缓冲区都有一定的容量。只要字符串缓冲区所包含的字符序列的长度没有超出此容量,就无需分配新的内部缓冲区数组。如果内部缓冲区溢出,则此容量自动增大。从 JDK 5 开始,为该类补充了一个单个线程使用的等价类,即 StringBuilder。与该类相比,通常应该优先使用 StringBuilder 类,因为它支持所有相同的操作,但由于它不执行同步,所以速度更快。

.
StringBuilder:一个可变的字符序列。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。如果可能,建议优先采用该类,因为在大多数实现中,它比 StringBuffer 要快。

在 StringBuilder 上的主要操作是 append 和 insert 方法,可重载这些方法,以接受任意类型的数据。每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符追加或插入到字符串生成器中。append 方法始终将这些字符添加到生成器的末端;而 insert 方法则在指定的点添加字符。

例如,如果 z 引用一个当前内容为 “start” 的字符串的生成器对象,则该方法调用 z.append(“le”) 将使字符串生成器包含 “startle”,而 z.insert(4, “le”) 将更改字符串生成器,使之包含 “starlet”。

通常,如果 sb 引用 StringBuilder 的实例,则 sb.append(x) 和 sb.insert(sb.length(), x) 具有相同的效果。每个字符串生成器都有一定的容量。只要字符串生成器所包含的字符序列的长度没有超出此容量,就无需分配新的内部缓冲区。如果内部缓冲区溢出,则此容量自动增大。

将 StringBuilder 的实例用于多个线程是不安全的。如果需要这样的同步,则建议使用 StringBuffer。

0 0