Java系列之 - String、StringBuilder、S

2020-05-07  本文已影响0人  SupKing_a520

String


public final class String implements Serializable, Comparable<String>, CharSequence {
    private final char[] value;
    ......
}

final修饰的String 类,以及final修饰的char[] value,表示String类不可被继承,且value只能被初始化一次。这里的value变量其实就是存储了String字符串中的所有字符。

String a = new String("aa"):代表在堆内存中创建了一个字符串对象,变量a指向该对象,而该对象又指向常量池中的字符串常量aa。
String b = "bb":代表变量b直接指向常量池中的字符串常量bb,不会在堆内存中创建对象。

public String substring(int beginIndex) {
    if (beginIndex < 0) {
        throw new StringIndexOutOfBoundsException(beginIndex);
    }
    int subLen = value.length - beginIndex;
    if (subLen < 0) {
        throw new StringIndexOutOfBoundsException(subLen);
    }
    return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}

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

public String replace(char oldChar, char newChar) {
    if (oldChar != newChar) {
        int len = value.length;
        int i = -1;
        char[] val = value; /* avoid getfield opcode */
        while (++i < len) {
            if (val[i] == oldChar) {
                break;
            }
        }
        if (i < len) {
            char buf[] = new char[len];
            for (int j = 0; j < i; j++) {
                buf[j] = val[j];
            }
            while (i < len) {
                char c = val[i];
                buf[i] = (c == oldChar) ? newChar : c;
                i++;
            }
            return new String(buf, true);
        }
    }
    return this;
}

我们可以看到,String类的substring方法,concat方法,replace方法,都是内部重新生成一个String对象的。这也就是为什么我们如果采用String对象频繁的进行拼接,截取,替换操作效率很低下的原因。

StringBuilder


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

StringBuilder类继承AbstractStringBuilder抽象类,其中StringBuilder的大部分方法都是直接调用的父类的实现。

public StringBuilder() {
    super(16);
}

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

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

可以看出StringBuilder的默认初始容量是16,并且都调用了父类的构造方法。

public StringBuilder append(String str) {
    super.append(str);
    return this;
}
    
public StringBuilder delete(int start, int end) {
    super.delete(start, end);
    return this;
}
    
public StringBuilder insert(int offset, String str) {
    super.insert(offset, str);
    return this;
}
    
public int indexOf(String str) {
    return super.indexOf(str);
}

public StringBuilder reverse() {
    super.reverse();
    return this;
}

再来看他的一些操作方法也都是调用了父类的实现,接下来我们就看看父类的具体实现。

abstract class AbstractStringBuilder implements Appendable, CharSequence {
 
    char[] value;
    int count;

    AbstractStringBuilder() {}

    AbstractStringBuilder(int capacity) {
        value = new char[capacity];
    }
}

char[] value没有final修饰,代表它是可以扩展的。接下来我们重点看一下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;
}

首先判断是否为null,null也是可以append进去的。

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

接着判断数组容量是否满足此次append,不满足的话执行扩容:尝试将新容量扩为大小变成2倍+2,如果不够,直接扩充到需要的容量大小,最大容量为MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8。

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

private int newCapacity(int minCapacity) {
    //尝试将新容量扩为大小变成2倍+2,如果不够,直接扩充到需要的容量大小。
    int newCapacity = (value.length << 1) + 2;
    if (newCapacity - minCapacity < 0) {
        newCapacity = minCapacity;
    }
    return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
            ? hugeCapacity(minCapacity)
            : newCapacity;
}

private int hugeCapacity(int minCapacity) {
    if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
        throw new OutOfMemoryError();
    }
    return (minCapacity > MAX_ARRAY_SIZE)
            ? minCapacity : MAX_ARRAY_SIZE;
}

紧接着通过getChars调native方法getCharsNoCheck实现真正的append

public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
    //首先做一些参数校验,这里就省略了
    ......
    getCharsNoCheck(srcBegin, srcEnd, dst, dstBegin);
}

@FastNative
native void getCharsNoCheck(int start, int end, char[] buffer, int index);

整个StringBuilder的append方法,本质上是调用System的native方法,直接将String 类型的str字符串中的字符数组,拷贝到了StringBuilder的字符数组中。

最后说下StringBuilder的toString方法:

@Override
public String toString() {
    if (count == 0) {
        return "";
    }
    return StringFactory.newStringFromChars(0, count, value);
}

这里的toString方法直接new 一个String对象,将StringBuilder对象的value进行一个拷贝,重新生成一个对象,不共享之前StringBuilder的char[]。

以上就是StringBuilder的拼接字符串的原理分析,可以发现没有像String一样去重新new 对象,所以在频繁的拼接字符上,StringBuilder的效率远远高于String类。

StringBuffer


public final class StringBuffer
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence {
    
    //transient关键字的作用:被修饰的成员属性变量不被序列化
    private transient char[] toStringCache;
    static final long serialVersionUID = 3388685877147921107L;

    public StringBuffer() {
        super(16);
    }

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

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

其构造方法和成员基本和StringBuilder一样,唯一区别就是char[]不允许序列化。

@Override
public synchronized StringBuffer append(String str) {
    toStringCache = null;
    super.append(str);
    return this;
}

其对应的内部方法都加了synchronized关键字,所以是线程安全的数据结构。

@Override
public synchronized String toString() {
    if (toStringCache == null) {
        toStringCache = Arrays.copyOfRange(value, 0, count);
    }
    return new String(toStringCache, 0, count);
}

如果StringBuffer对象此时存在toStringCache,在多次调用其toString方法时,其new出来的String对象是会共享同一个char[] 内存的,达到共享的目的。但是StringBuffer只要做了修改,其toStringCache属性值都会置null处理。这也是StringBuffer和StringBuilder的一个区别点。

总结


String 类不可变,内部维护的char[] 数组长度不可变,为final修饰,String类也是final修饰,不存在扩容。字符串拼接,截取,都会生成一个新的对象。频繁操作字符串效率低下,因为每次都会生成新的对象。

StringBuilder 类内部维护可变长度char[] , 初始化数组容量为16,存在扩容, 其append拼接字符串方法内部调用System的native方法,进行数组的拷贝,不会重新生成新的StringBuilder对象。非线程安全的字符串操作类, 其每次调用 toString方法而重新生成的String对象,不会共享StringBuilder对象内部的char[],会进行一次char[]的copy操作。

StringBuffer 类内部维护可变长度char[], 基本上与StringBuilder一致,但其为线程安全的字符串操作类,大部分方法都采用了Synchronized关键字修改,以此来实现在多线程下的操作字符串的安全性。其toString方法而重新生成的String对象,会共享StringBuffer对象中的toStringCache属性(char[]),但是每次的StringBuffer对象修改,都会置null该属性值。

上一篇下一篇

猜你喜欢

热点阅读