美文网首页
Java系列之 - String、StringBuilder、S

Java系列之 - String、StringBuilder、S

作者: SupKing_a520 | 来源:发表于2020-05-07 19:31 被阅读0次

    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该属性值。

    相关文章

      网友评论

          本文标题:Java系列之 - String、StringBuilder、S

          本文链接:https://www.haomeiwen.com/subject/dyrzghtx.html