1. String 不可变的原因

直接上源码(摘自 JDK 11.0.10 )— 仅取部分源码非全部

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
@Stable
private final byte[] value;
}

从源码中可以看到 String 类中使用 final 修饰一个 byte[] 来保存字符串,因此 String 对象是不可变的。


2. StringBuilder

源码(摘自 JDK 11.0.10)— 仅取部分源码非全部

public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, Comparable<StringBuilder>, CharSequence
{

/**
* Constructs a string builder with no characters in it and an
* initial capacity of 16 characters.
*/
@HotSpotIntrinsicCandidate
public StringBuilder() {
super(16);
}

/**
* Constructs a string builder with no characters in it and an
* initial capacity specified by the {@code capacity} argument.
*
* @param capacity the initial capacity.
* @throws NegativeArraySizeException if the {@code capacity}
* argument is less than {@code 0}.
*/
@HotSpotIntrinsicCandidate
public StringBuilder(int capacity) {
super(capacity);
}

/**
* Constructs a string builder initialized to the contents of the
* specified string. The initial capacity of the string builder is
* {@code 16} plus the length of the string argument.
*
* @param str the initial contents of the buffer.
*/
@HotSpotIntrinsicCandidate
public StringBuilder(String str) {
super(str.length() + 16);
append(str);
}

/**
* Constructs a string builder that contains the same characters
* as the specified {@code CharSequence}. The initial capacity of
* the string builder is {@code 16} plus the length of the
* {@code CharSequence} argument.
*
* @param seq the sequence to copy.
*/
public StringBuilder(CharSequence seq) {
this(seq.length() + 16);
append(seq);
}

}

从上述源码中可以看到 StringBuilder 继承于 AbstractStringBuilder 类,所以继续跟踪,查看 AbstractStringBuilder 类的源码( 仅取部分源码非全部)如下:

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

/**
* The id of the encoding used to encode the bytes in {@code value}.
*/
byte coder;

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

private static final byte[] EMPTYVALUE = new byte[0];

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

/**
* Creates an AbstractStringBuilder of the specified capacity.
*/
AbstractStringBuilder(int capacity) {
if (COMPACT_STRINGS) {
value = new byte[capacity];
coder = LATIN1;
} else {
value = StringUTF16.newBytesFor(capacity);
coder = UTF16;
}
}
}

​ 从 AbstractStringBuilder 类中可以发现,其使用 byte[] 来存储字符串(没有被 final 修饰),所以 StringBuilder 的对象是可变的。从 StringBuilder 的源码中也可以看出,StringBuilder 的构造方法实际上使用的是父类 AbstractStringBuilder 的构造方法,并且可以从中看出,默认 StringBuilder 对象的大小为 16,如果给定初始化对象,则初始大小为:初始化对象长度 + 16。


3. StringBuffer

源码(摘自 JDK 11.0.10)— 仅取部分源码非全部

public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, Comparable<StringBuffer>, CharSequence
{

/**
* A cache of the last value returned by toString. Cleared
* whenever the StringBuffer is modified.
*/
private transient String toStringCache;

/** use serialVersionUID from JDK 1.0.2 for interoperability */
static final long serialVersionUID = 3388685877147921107L;

/**
* Constructs a string buffer with no characters in it and an
* initial capacity of 16 characters.
*/
@HotSpotIntrinsicCandidate
public StringBuffer() {
super(16);
}

/**
* Constructs a string buffer with no characters in it and
* the specified initial capacity.
*
* @param capacity the initial capacity.
* @throws NegativeArraySizeException if the {@code capacity}
* argument is less than {@code 0}.
*/
@HotSpotIntrinsicCandidate
public StringBuffer(int capacity) {
super(capacity);
}

/**
* Constructs a string buffer initialized to the contents of the
* specified string. The initial capacity of the string buffer is
* {@code 16} plus the length of the string argument.
*
* @param str the initial contents of the buffer.
*/
@HotSpotIntrinsicCandidate
public StringBuffer(String str) {
super(str.length() + 16);
append(str);
}

/**
* Constructs a string buffer that contains the same characters
* as the specified {@code CharSequence}. The initial capacity of
* the string buffer is {@code 16} plus the length of the
* {@code CharSequence} argument.
* <p>
* If the length of the specified {@code CharSequence} is
* less than or equal to zero, then an empty buffer of capacity
* {@code 16} is returned.
*
* @param seq the sequence to copy.
* @since 1.5
*/
public StringBuffer(CharSequence seq) {
this(seq.length() + 16);
append(seq);
}

@Override
public synchronized int compareTo(StringBuffer another) {
return super.compareTo(another);
}

@Override
public synchronized int length() {
return count;
}

@Override
public synchronized int capacity() {
return super.capacity();
}


@Override
public synchronized void ensureCapacity(int minimumCapacity) {
super.ensureCapacity(minimumCapacity);
}

/**
* @since 1.5
*/
@Override
public synchronized void trimToSize() {
super.trimToSize();
}

/**
* @throws IndexOutOfBoundsException {@inheritDoc}
* @see #length()
*/
@Override
public synchronized void setLength(int newLength) {
toStringCache = null;
super.setLength(newLength);
}

/**
* @throws IndexOutOfBoundsException {@inheritDoc}
* @see #length()
*/
@Override
public synchronized char charAt(int index) {
return super.charAt(index);
}

/**
* @throws IndexOutOfBoundsException {@inheritDoc}
* @since 1.5
*/
@Override
public synchronized int codePointAt(int index) {
return super.codePointAt(index);
}

/**
* @throws IndexOutOfBoundsException {@inheritDoc}
* @since 1.5
*/
@Override
public synchronized int codePointBefore(int index) {
return super.codePointBefore(index);
}

/**
* @throws IndexOutOfBoundsException {@inheritDoc}
* @since 1.5
*/
@Override
public synchronized int codePointCount(int beginIndex, int endIndex) {
return super.codePointCount(beginIndex, endIndex);
}

/**
* @throws IndexOutOfBoundsException {@inheritDoc}
* @since 1.5
*/
@Override
public synchronized int offsetByCodePoints(int index, int codePointOffset) {
return super.offsetByCodePoints(index, codePointOffset);
}

/**
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
@Override
public synchronized void getChars(int srcBegin, int srcEnd, char[] dst,
int dstBegin)
{
super.getChars(srcBegin, srcEnd, dst, dstBegin);
}

/**
* @throws IndexOutOfBoundsException {@inheritDoc}
* @see #length()
*/
@Override
public synchronized void setCharAt(int index, char ch) {
toStringCache = null;
super.setCharAt(index, ch);
}
}

​ 从源码中可以看到 StringBuffer 同样继承于 AbstractStringBuilder 类。所以在对象可变性上,StringBufferStringBuilder 相同,在对 byte[] 进行初始化时和 StringBuilder 相同。即默认对象的大小为 16,当给定初始化对象时,则初始大小为:初始化对象长度 + 16。
​ 从 StringBuffer 的方法中可以看到 所有的方法加了 synchronized 关键字——同步锁,所以 StringBuffer 是线程安全的。


4. StringBuilder / StringBuffer 区别

  • StringBuilderStringBuffer 因为对象可变,所以两种修改都是对对象本身的修改。
  • StringBuilder 是线程不安全的,StringBuffer 是线程安全的 (方法采用 synchronized 实现同步)
  • StringBuilder 相比于 StringBuffer 速度提升了 10% ~ 15% (未实验证明,数据来源于 Java-Guide 博客)

5. 总结

  • String: 对字符串的编辑较少时使用(对String对象进行修改时,每次都会生成新对象,引用指向新对象 )
  • StringBuilder: 单线程情况下,需要对字符串进行多次编辑
  • StringBuffer: 多线程情况下,需要对字符串进行多次编辑