Java源码分析--StringBuffer/StringBuilder

在Java中,使用StringBuffer和StringBuilder这两个类表示可变长字符串,这篇博文用来探讨这两个类的源码分析,所有源码都是开源的,附上版权要求:

/*
 * Copyright (c) 1994, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

继承关系

这两个类的继承关系完全一样,都是如下所示:
public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
 public final class StringBuffer
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
在继承关系中可以看到,都继承了一个叫做AbstractStringBuilder的类,下文会讨论这个类,还有就是实现了可串行和字符序列接口,没什么可说的。

内部数据结构

String和这两个可变长字符串在内部用于存储字符串的数据结构都是char数组,只不过String的char数组是由final修饰了,一旦赋值不能被修改,而这两个类的字符串都是由普通char数组实现的,见下面源码:
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];
abstract class AbstractStringBuilder implements Appendable, CharSequence {
    /**
     * The value is used for character storage.
     */
    char[] value;

AbstractStringBuilder类介绍

这个类是一个抽象类,我们知道StringBuffer是线程安全的,StringBuilder是非线程安全的,而两个类的功能除了这点区别之外,并没有区别,通过查看源码可知,两个类中几乎所有的方法都是拿了AbstractStriingBuilder的实现,譬如常用的字符串追加和扩容等方法,如下所示:
AbstractStringBuilder的append方法:
public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }
接下来依次是StringBuffer和StringBuilder的append方法:
 @Override
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }
@Override
    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }
都是调用了super方法,另外,除了一个是同步方法一个不是同步方法之外,还有一个区别就是StringBuffer中多了一个toStringCache这个东西,这个东西是StringBuffer中的一个char数组,用来缓存最近一次的toString,当然,每次对字符串进行更改,这个缓存都要作废,在下一次toStrinig后进行重新缓存,其声明如下:
 /**
     * A cache of the last value returned by toString. Cleared
     * whenever the StringBuffer is modified.
     */
    private transient char[] toStringCache;
就总的而言,StringBuffer和StringBuilder就两方面的不同,一是StringBuffer是线程安全的,StringBuilder是非线程安全的,二是两者的toString方法不同

toString比较

StringBuffer的toString方法如下:
 @Override
    public synchronized String toString() {
        if (toStringCache == null) {
            toStringCache = Arrays.copyOfRange(value, 0, count);
        }
        return new String(toStringCache, true);
    }
StringBuilder的toString方法如下:
 @Override
    public String toString() {
        // Create a copy, don't share the array
        return new String(value, 0, count);
    }
两个toString都是每次生成字符串都是新生成一个字符串,即使字符串没有更改,两次生成的字符串也是不同的两个。再有一点不同就是,StringBuffer的toString方法调用了一个带有两个参数的String构造器,这个平常很少见,而且有包访问限制,其定义如下:
/*
    * Package private constructor which shares value array for speed.
    * this constructor is always expected to be called with share==true.
    * a separate constructor is needed because we already have a public
    * String(char[]) constructor that makes a copy of the given char[].
    */
    String(char[] value, boolean share) {
        // assert share : "unshared not supported";
        this.value = value;
    }
那直接使用字节数组进行String的创建是怎么完成的呢?
 /**
     * Allocates a new {@code String} that contains characters from a subarray
     * of the character array argument. The {@code offset} argument is the
     * index of the first character of the subarray and the {@code count}
     * argument specifies the length of the subarray. The contents of the
     * subarray are copied; subsequent modification of the character array does
     * not affect the newly created string.
     *
     * @param  value
     *         Array that is the source of characters
     *
     * @param  offset
     *         The initial offset
     *
     * @param  count
     *         The length
     *
     * @throws  IndexOutOfBoundsException
     *          If the {@code offset} and {@code count} arguments index
     *          characters outside the bounds of the {@code value} array
     */
    public String(char value[], int offset, int count) {
        if (offset < 0) {
            throw new StringIndexOutOfBoundsException(offset);
        }
        if (count <= 0) {
            if (count < 0) {
                throw new StringIndexOutOfBoundsException(count);
            }
            if (offset <= value.length) {
                this.value = "".value;
                return;
            }
        }
        // Note: offset or count might be near -1>>>1.
        if (offset > value.length - count) {
            throw new StringIndexOutOfBoundsException(offset + count);
        }
        this.value = Arrays.copyOfRange(value, offset, offset+count);
    }
比较这两个构造器就会发现,StringBuffer的构造器在构造String的时候并没有复制字节数组而是直接指向的,StringBuilder是复制了一遍,从这一点来说,StringBuffer的toString方法性能更好。

区别总结

1.(相同)StringBuffer和StringBuilder有完全相同的继承关系并且在功能上没有什么区别,而且内部方法的实现几乎全部都是使用了相同的实现,都是继承自AbstractStringBuilder
2.(不同点)StringBuffer是线程安全的,StringBuilder是非线程安全的,由于方法的实现几乎相同,实现线程安全与否,取决于对父类方法封装的时候,是使用了同步方法还是普通方法
3.(不同点)StringBuffer的toString方法的性能比StringBuilder的更好

AbstractStringBuilder关键方法解析

1.扩容
 /**
     * 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);
    }
主要工作有两个:一是先将原来的数组长度乘二加二和指定容量相比,取大的作为实际要扩容到的容量,二是进行数组的复制。关于这个Array.copyOf方法,会在其他博文中探讨,OK,先简单说明以下,这个数组拷贝的方法速度是比较快的,是用了本地方法实现的,但是注意是浅拷贝
2.字符串追加
/**
     * Appends the specified string to this character sequence.
     * <p>
     * The characters of the {@code String} argument are appended, in
     * order, increasing the length of this sequence by the length of the
     * argument. If {@code str} is {@code null}, then the four
     * characters {@code "null"} are appended.
     * <p>
     * Let <i>n</i> be the length of this character sequence just prior to
     * execution of the {@code append} method. Then the character at
     * index <i>k</i> in the new character sequence is equal to the character
     * at index <i>k</i> in the old character sequence, if <i>k</i> is less
     * than <i>n</i>; otherwise, it is equal to the character at index
     * <i>k-n</i> in the argument {@code str}.
     *
     * @param   str   a string.
     * @return  a reference to this object.
     */
    public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }
OK,也是有两件事儿:一是判断剩余空间够不够用了,如果不够用就要扩容,如果够用直接追加。使用的时候,有一个性能和空间占用的权衡,因为扩容是耗时耗性能的操作,如果容量太小(默认是16char)每次追加都要扩容的话,性能就很底下咯,如果一开始就大致确定了字符串大小,虽然耗空间,但是省时间,权衡
3.append和字符串连接符『+』性能比较
说白了字符串连接符『+』被编译之后,使用的还是append方法,而且是StringBuilder的,并且每次连接都要创建一个StringBuilder对象然后进行append,由此说来,字符串连接符的有点就是编写代码的时候方便,当然,有一种情况例外,譬如:
String s1 = "hello"+" "+"world";
这个编译完之后就连接好了,对于非这种情况下的,都是上面解释的那样,使用append方法
使用『+』的缺点也很明显,除了书写方便并没有什么优点,主要缺点有两个:
1.如果有大量的字符串连接符的使用,会造成大量StringBuilder类的创建,导致性能低下
2.由于创建StringBuilder类是内部完成的,如果被连接的字符串超过了16char,则在连接的时候还要进行扩容,性能更低

记忆顺序

虽说是理科生,而且死记硬背也是不提倡的,但是对于这种功能类似或相同的类,使用一定的记忆顺序能够避免很多麻烦,可以较快的记住所学的内容,毕竟学到就要记到嘛,顺序如下:
实现原理+相同点+不同点+使用注意事项
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值