JDK1.8源码笔记(3) StringBuilder&StringBuffer

AbstractStringBuilder

前言

在讨论StringBuilder和StringBuffer之前,我们有必要先说一下AbstractStringBuilder。

StringBuilder和StringBuffer都继承自AbstractStringBuilder,AbstractStringBuilder也定义了大量的方法和变量。

A mutable sequence of characters.
Implements a modifiable string.

另外可以观察到这个类是一个抽象类,不可以实例化,实现了Appendable和CharSequence接口。

Appendable中重载了好几个append方法,
Appendable append(CharSequence csq) throws IOException;
Appendable append(CharSequence csq, int start, int end) throws IOException;
Appendable append(char c) throws IOException;
虽然没有直接定义和参数和String相关的方法,但正是因为String也继承了CharSequence,所以可以传入String,甚至可以传入StringBuffer和StringBuilder。

CharSequence我们很熟悉了,String、StringBuilder和StringBuffer都实现了这个接口。

构造方法

/**
 * This no-arg constructor is necessary for serialization of subclasses.
 */
AbstractStringBuilder() {
}

子类如果能够序列化的话,其父类需要有无参的构造函数。


/**
 * Creates an AbstractStringBuilder of the specified capacity.
 */
AbstractStringBuilder(int capacity) {
    value = new char[capacity];
}

在构造函数中决定缓冲区的空间大小。即决定初始空间的大小。

成员变量&成员方法

char[] value;
The value is used for character storage.
和String一样,也使用char数组存储实际的字符信息。

public int capacity() {
    return value.length;
}

获得capacity,即容量。

int count;
The count is the number of characters used.
StringBuilder/StringBuffer的工作方式并不是需要多大空间就开多大空间,而是预先开出一定的空间供使用,所以如果没有使用满,自然需要一个变量去记录已经使用的空间是多大。

public int length() {
    return count;
}

获得使用量,也就是实际长度,这个方法是定义在CharsSequence中的。

public void ensureCapacity(int minimumCapacity) {
    ......
}

这里省略方法体和里面的复杂调用。
* Ensures that the capacity is at least equal to the specified minimum.
* If the current capacity is less than the argument, then a new internal
* array is allocated with greater capacity. The new capacity is the
* larger of:
* <ul>
* <li>The {@code minimumCapacity} argument.
* <li>Twice the old capacity, plus {@code 2}.
这个方法的功能是当当前容量小于minimumCapacity,且minimumCapacity合法的话,修改容量大小。否则没有变化。
注意,并不是直接把容量修改为minimumCapacity的值。如果当前容量的两倍小于minimumCapacity,则开到minimumCapacity大小的空间,否则把容量扩充为当前的两倍。
换言之,就是最少扩大为原来的两倍。

public void trimToSize() {
    if (count < value.length) {
        value = Arrays.copyOf(value, count);
    }
}

缩小容量到实际大小,是开了一个新数组的,丢弃的数组会被GC回收。

public void setLength(int newLength) {
    if (newLength < 0)
        throw new StringIndexOutOfBoundsException(newLength);
    ensureCapacityInternal(newLength);

    if (count < newLength) {
        Arrays.fill(value, count, newLength, '\0');
    }

    count = newLength;
}

修改数组的实际长度,如果空间不够会首先开足够空间,新增空间的规则和上面讲得一样。新增的长度用\0填充。

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;
}

// Documentation in subclasses because of synchro difference
public AbstractStringBuilder append(StringBuffer sb) {
    if (sb == null)
        return appendNull();
    int len = sb.length();
    ensureCapacityInternal(count + len);
    sb.getChars(0, len, value, count);
    count += len;
    return this;
}

这里可以看到最常用的append方法是怎么实现的,先确保空间足够,再把新串复制过去。

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--;
    return this;
}

删除指定位置的字符, 可以看到是把index后面的整体向前移动一个位置。
public AbstractStringBuilder insert(int offset, String str) {
    if ((offset < 0) || (offset > length()))
        throw new StringIndexOutOfBoundsException(offset);
    if (str == null)
        str = "null";
    int len = str.length();
    ensureCapacityInternal(count + len);
    System.arraycopy(value, offset, value, offset + len, count - offset);
    str.getChars(value, offset);
    count += len;
    return this;
}

可以看到插入的原理,先判断空间是否足够, 然后将插入位置后面的字符后移,再把欲插入字符复制进去。
public AbstractStringBuilder reverse() {
    boolean hasSurrogates = false;
    int n = count - 1;
    for (int j = (n-1) >> 1; j >= 0; j--) {
        int k = n - j;
        char cj = value[j];
        char ck = value[k];
        value[j] = ck;
        value[k] = cj;
        if (Character.isSurrogate(cj) ||
                Character.isSurrogate(ck)) {
            hasSurrogates = true;
        }
    }
    if (hasSurrogates) {
        reverseAllValidSurrogatePairs();
    }
    return this;
}

String中并没有定义字符串反转相关的方法,所以想要实现反转需要关注这个方法。
这个方法可以说是非常精巧了,j为中心靠低下标的字符,k为中心靠高下标的字符。像两边扩展进行交换。

StringBuilder

前言

A mutable sequence of characters.
开宗明义,和String最大的区别。

This class provides an API compatible with {@code StringBuffer}, but with no guarantee of synchronization.
功能上和StringBuffer兼容,只是线程不安全。
说明这两个类非常常用,为了性能的考虑可以自由选择。

* This class is designed for use as a drop-in replacement for
* {@code StringBuffer} in places where the string buffer was being
* used by a single thread (as is generally the case).   Where possible,
* it is recommended that this class be used in preference to
* {@code StringBuffer} as it will be faster under most implementations.
后面也可也可以看到,这两个类其实是实现了同一个AbstractStringBuilder接口的,所以设计者希望这两个类可以自由地互相替换。

* In general, if sb refers to an instance of a {@code StringBuilder},
* then {@code sb.append(x)} has the same effect as
* {@code sb.insert(sb.length(), x)}.

* Every string builder has a capacity. As long as the length of the
* character sequence contained in the string builder does not exceed
* the capacity, it is not necessary to allocate a new internal
* buffer. If the internal buffer overflows, it is automatically made larger.
每一个builder是有容量的。如果超过容量,会自动扩容。

* <p>Unless otherwise noted, passing a {@code null} argument to a constructor
* or method in this class will cause a {@link NullPointerException} to be
* thrown.
构造方法传null会抛出异常。

继承类&实现接口

StringBuilder继承了AbstractStringBuilder,并且实现了java.io.Serializable和CharSequence接口。

StringBuilder和StringBuffer都继承了AbstractStringBuilder这个父类。

CharSequence我们前面讲过,定义了包括length、charAt、subSequence和toString等方法。

Serializable会在后面详细解释。

构造方法

public StringBuilder() {
    super(16);
}

public StringBuilder(int capacity) {
    super(capacity);
}

public StringBuilder(String str) {
    super(str.length() + 16);
    append(str);
}

可以指定初始化开的容量大小,默认大小是16个字符。
如果是用字符串初始化,也会默认多开16的空间。

成员变量&成员方法

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

toString方法特别标明,为了保持String的不变性,并没有共享字符数组的空间。

StringBuilder中大量的方法其实就是对AbstractStringBuilder的薄封装,没有太多看点。

StringBuffer

前言

* String buffers are safe for use by multiple threads. The methods
* are synchronized where necessary so that all the operations on any
* particular instance behave as if they occur in some serial order
* that is consistent with the order of the method calls made by each of
* the individual threads involved.
StringBuffer线程安全。

* Whenever an operation occurs involving a source sequence (such as
* appending or inserting from a source sequence), this class synchronizes
* only on the string buffer performing the operation, not on the source.
* Note that while {@code StringBuffer} is designed to be safe to use
* concurrently from multiple threads, if the constructor or the
* {@code append} or {@code insert} operation is passed a source sequence
* that is shared across threads, the calling code must ensure
* that the operation has a consistent and unchanging view of the source
* sequence for the duration of the operation.
* This could be satisfied by the caller holding a lock during the
* operation's call, by using an immutable source sequence, or by not
* sharing the source sequence across threads.
大意就是说,虽然能够在操作source sequence这件事情上保持同步,但是在source sequence本身并没有保证同步。这就意味着如果你能通过别的方式引用source sequence,还是会有线程不安全的风险。
解决的方案如下:
1 by using an immutable source sequence 
2 or by not sharing the source sequence across threads.

继承类&实现接口

同StringBuilder一样,继承AbstractStringBuilder类,并且实现java.io.Serializable和CharSequence接口。

构造方法

public StringBuffer() {
    super(16);
}
public StringBuffer(int capacity) {
    super(capacity);
}
public StringBuffer(String str) {
    super(str.length() + 16);
    append(str);
}
同StringBuilder的构造方法类似,开得空间也相同。要注意构造方法并没有同步机制,所以在单例模式的时候可能需要使用者来进行实现。

成员变量&成员方法

/**
 * A cache of the last value returned by toString. Cleared
 * whenever the StringBuffer is modified.
 */
private transient char[] toStringCache;
这个变量在StringBuilder中是没有的。
这个方法的作用是用来缓存上一次StringBuffer的toString方法的返回值。
如果哪个方法中修改了StringBuffer,则会把toStringCache给设为null。

首先我们需要比较以下StringBuffer和StringBuilder的toString方法:
@Override
public synchronized String toString() {
    if (toStringCache == null) {
        toStringCache = Arrays.copyOfRange(value, 0, count);
    }
    return new String(toStringCache, true);
}

上面这个方法中调用了下面这个String的构造方法:
String(char[] value, boolean share) {
    // assert share : "unshared not supported";
    this.value = value;
}

可以看到这个构造方法只是单纯引用了StringBuffer的值,而并没有新开一个数组,这是很特别的情况。当然如果toStringCache为null的话,也是直接返回了一个全新的String的。

@Override
public String toString() {
    // Create a copy, don't share the array
    return new String(value, 0, count);
}
public String(char value[], int offset, int count) {
    ......
    this.value = Arrays.copyOfRange(value, offset, offset+count);
}

而在StringBuilder方法中,则是直接拷贝产生了一个新的数组。
并且toString中解释道Create a copy, don't share the array。

再联系到StringBuffer和StringBuilder的区别我们可以想到:
StringBuilder多用在单线程或者局部作用域中,StringBuffer用得多线程中。
StringBuffer的声明周期可能比较长,并且使用到的地方会比较多,使用这样一个缓存的方式,可以在一定程度减少空间和性能的消耗。
https://stackoverflow.com/questions/46294579/why-stringbuffer-has-a-tostringcache-while-stringbuilder-not

另外还要解释一下transient:
Variables may be marked transient to indicate that they are not part of the persistent state of an object.
其实这个变量还是和序列化相关的,因为这个变量只是一个cache,所以不需要保存。
https://stackoverflow.com/questions/910374/why-does-java-have-transient-fields

其实比较StringBuffer和StringBuilder,区别并不是很大,最大的区别就是在StringBuffer中,大多数修改char数组的方法都被synchronized修饰。

并且在append和insert等修改StringBuffer方法中,需要对toStringCache设为null。

不过有特殊情况在于有些insert方法是并没有被synchronized修饰的。例如下面这个方法:
@Override
public  StringBuffer insert(int offset, boolean b) {
    // Note, synchronization achieved via invocation of StringBuffer insert(int, String)
    // after conversion of b to String by super class method
    // Ditto for toStringCache clearing
    super.insert(offset, b);
    return this;
}

注释大概的意思是,这个方法的同步最终是通过StringBuffer insert(int, String)进行实现的。我们大概追踪一下调用过程:
首先这个方法调用了父类的insert方法,

public AbstractStringBuilder insert(int offset, boolean b) {
    return insert(offset, String.valueOf(b));
}

而父类中调用的这个方法,其实在StringBuffer中被重写,所以调用的是StringBuffer中重写后并且被synchronized修饰的方法:
@Override
public synchronized StringBuffer insert(int offset, String str) {
    toStringCache = null;
    super.insert(offset, str);
    return this;
}

除了boolean,其他的例如float、int、double、long等在插入过程中调用toString方法的类型也是这种情况。但是char不是这种情况,char直接在insert方法上修饰synchronized,因为char在父类中不是使用valueOf进行实现的。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值