Skip to content

Java String StringBuffer StringBuilder

Posted on:March 30, 2021 at 15:31:30 GMT+8

JDK1.8

概述

String 对象是不可变的。内部用一个 final 的 char 数组 value(private final char value[];)来存储字符串中的每个字符。String 的 concat、substring 方法都是 new 了一个新的 String 对象。

StringBuffer since JDK1.0,用来实现可变的字符串。StringBuffer 是线程安全的,大多数方法都被 synchronized 关键字修饰过。StringBuffer 中的比较主要的两个方法是 append 和 insert。

StringBuilder since JDK1.5。用来实现可变的字符串,但方法没有用 synchronized 关键字修饰,因此线程不安全,但效率更高。

String

常量池

public static void main(String[] args) {
    String str = "ab";
    String str1 = "a" + "b";
    String str2 = new String("ab");
    String str3 = "ab".intern();
    String str4 = "a".intern() + "b".intern();
    String str5 = new String("ab").intern();
    System.out.println(str == str1); // true
    System.out.println(str == str2); // false
    System.out.println(str == str3); // true
    System.out.println(str == str4); // false
    System.out.println(str == str5); // true
}

运行时常量池是方法区的一部分,Class 文件中常量池表中的字符串字面量将在类加载后存放到运行时常量池。使用 new 的将会在 Java 堆中分配。

intern() 方法注释:

返回字符串对象的规范表示。

一个字符串池,最初是空的,由 String 类私有地维护。

当调用 intern 方法时,如果池中已经包含了一个由 equals(Object) 方法确定的与这个 String 对象相等的字符串,那么将返回池中的字符串。否则,这个 String 对象将被添加到池中,并返回对这个 String 对象的引用。

由此可见,对于任何两个字符串 s 和 t,如果且仅当 s.equals(t) 为真时, s.intern() == t.intern() 为真。

所有字面字符串和字符串值的常量表达式都会被 intern。字符串字面量的定义在 The Java™ 语言规范的 3.10.5 节中。

String#concat

public String concat(String str) {
    int otherLen = str.length();
    if (otherLen == 0) {
        return this;
    }
    int len = value.length;
    char buf[] = Arrays.copyOf(value, len + otherLen);
    str.getChars(buf, len);
    return new String(buf, true);
}

Arrays.copyOf(char[], int) 用来扩大一个数组的长度,返回一个新的字符数组:

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

new 了一个新的长度的 char[],再调用 System.arrayCopy()

public static native void arraycopy(Object src,  int srcPos,
                                    Object dest, int destPos,
                                    int length);

是一个 native 方法,将 src 从 srcPos 位置 copy 到 dest 的 destPos 开始,copy length 长度。

上面我们 copy 了二者长度的较小值,得到了新的 buf[]。接着调用str.getChars(buf, len);

void getChars(char dst[], int dstBegin) {
    System.arraycopy(value, 0, dst, dstBegin, value.length);
}

将要添加的字符串 copy 到处于 buf[]原字符串 len 之后的位置,copy 要添加的字符串的长度这么长。

最后以 buf[] 为参数构造了一个新的字符串返回。

StringBuilder

field

从 AbstractStringBuilder 中继承:

char[] value; 存储 SB 中的字符。

int count; value 数组中已经使用的数量。

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

构造方法

SB 初始容量是 16。

public StringBuilder() {
    super(16);
}

StringBuilder#append

@Override
public StringBuilder append(String str) {
    super.append(str);
    return this;
}
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;
}

如果传入的是 null,转换为字符串插进去:

private AbstractStringBuilder appendNull() {
    int c = count;
    ensureCapacityInternal(c + 4);
    final char[] value = this.value;
    value[c++] = 'n';
    value[c++] = 'u';
    value[c++] = 'l';
    value[c++] = 'l';
    count = c;
    return this;
}

构造方法传 null 会报 NPE。

ensureCapacityInternal 也是用 Arrays.copyOf 增加长度:

private void ensureCapacityInternal(int minimumCapacity) {
    // overflow-conscious code
    if (minimumCapacity - value.length > 0) {
        value = Arrays.copyOf(value,
                newCapacity(minimumCapacity));
    }
}

新的长度一般是原长度的 2 倍 + 2:

private int newCapacity(int minCapacity) {
    // overflow-conscious code
    int newCapacity = (value.length << 1) + 2;
    if (newCapacity - minCapacity < 0) {
        newCapacity = minCapacity;
    }
    return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
        ? hugeCapacity(minCapacity)
        : newCapacity;
}

接着调用了 String#getChars 的另一个重载方法:

public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
    if (srcBegin < 0) {
        throw new StringIndexOutOfBoundsException(srcBegin);
    }
    if (srcEnd > value.length) {
        throw new StringIndexOutOfBoundsException(srcEnd);
    }
    if (srcBegin > srcEnd) {
        throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
    }
    System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}

str.getChars(0, len, value, count);将要添加的 str 的 value 数组 copy 给 SB 的 value,copy 到 count 之后的位置,copy len - 0 长度。将 str 接了上去。

下面更新 SB 中 value 数组已经使用的 count,返回更新后的 SB。

StringBuilder#toString

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

根据 SB 中的 value 中的 0 到 count 的数据构造一个 String 返回。

StringBuffer

大部分方法具体实现和 StringBuilder 差不多。

内部有一个 private transient char[] toStringCache;,用来缓存上一次 toString 返回的值,在 SB 修改时都会置 null。

@Override
public synchronized String toString() {
    if (toStringCache == null) {
        toStringCache = Arrays.copyOfRange(value, 0, count);
    }
    return new String(toStringCache, true);
}
public static char[] copyOfRange(char[] original, int from, int to) {
    int newLength = to - from;
    if (newLength < 0)
        throw new IllegalArgumentException(from + " > " + to);
    char[] copy = new char[newLength];
    System.arraycopy(original, from, copy, 0,
                     Math.min(original.length - from, newLength));
    return copy;
}

参考

源码

Java String 对象,你真的了解了吗?