美文网首页
String, StringBuilder,StringBuff

String, StringBuilder,StringBuff

作者: 老羊_肖恩 | 来源:发表于2018-02-10 19:48 被阅读31次

      这里我们只针对Java中的String、StringBuilder和StringBuffer的主要特征做相关描述,并通过源码对其实现原理做相应的解释。

    String

      我们都知道,在Java中String变量的内容是不可变的,字符串的内容在创建的时候就已经写死,后续对String的修改操作都是在拷贝原理字符串的基础上进行修改的,原来的字符串会不再被引用,直到被GC为止。那么为什么字符串的内容能保持不变呢?我们可以看一下源码:

    /** The value is used for character storage. */
    private final char value[];
    
    /** Cache the hash code for the string */
    private int hash; // Default to 0
    

      通过源码,我们知道String类主要定义了两个基本属性,其中value[]被定义成一个final类型的私有字符数组,该字符数组用来存储字符串的实际内容,并且String类只提供了初始化方法来直接操作该数组,因此该字符数组一旦被初始化,其内容将无法修改(因为压根就没有修改这个数组的接口)。下面我们通过几个主要的构造方法来详细解释。

        public String() {
            this.value = "".value;
        }
    
        public String(String original) {
            this.value = original.value;
            this.hash = original.hash;
        }
    
        public String(char value[]) {
            this.value = Arrays.copyOf(value, value.length);
        }
    

      String的无参构造方法String(),直接初始化了一个内容为空的字符串(有别于null),并将当前的value指向该空字符串的value。而String(String original)更是直接将当前要初始化的字符串的value和hash指向了原始字符串的value和hash内容(注意:这里没有直接return original,因为如果接下来将original指向null,当前字符串就会直接变成null)。String(char value[])方法直接将String内部的value指向了当前传递的字符数组的副本,这样做得目的,同样是为了防止当前数字的改变会引起字符串的内容改变。因此,我们可以看出来,Java中的String对象的内容是不可变的。

    StringBuilder

       StringBuilder提供了一种可变的不保证线程安全的字符序列的实现方式。该类继承自AbstractStringBuilder,很多关于操作都是直接调用父类的方法,我们不妨先看一下它的父类AbstractStringBuilder的实现原理。

    abstract class AbstractStringBuilder implements Appendable, CharSequence {
        /**
         * The value is used for character storage.
         */
        char[] value;
    
        /**
         * The count is the number of characters used.
         */
        int count;
    
        /**
         * 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];
        }
        
        ...
    
    }
    

      AbstractStringBuilder内部定义了一个默认可见性的字符数组,用于存储内容,另外还有一个默认的整型count,表示当前数组中字符的个数(注意:不是当前字符数组的大小,count<= value.length)。AbstractStringBuilder只提供了两个构造方法,无参的构造方法默认什么都不做,而构造方法AbstractStringBuilder(int capacity)只做了一件事,初始化当前字符数组的大小。而真正对当前AbstractStringBuilder的内容进行增加的是append()方法。我们只对其中一个方法public AbstractStringBuilder append(String str)进行详细解释:

    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,如果传入的参数不为空,首先要保证当前数组的容量能够容纳下所有字符的存储。这里用到了一个方法ensureCapacityInternal(count + len)。其代码如下:

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

      其实也很好理解,如果容积充足,什么都不做,否则拷贝当前字符数组,并进行扩容。在保证字符数组容积冲突的前提下,将传入的字符串的所有字符依次追加到当前字符数组的后面,实现如下:

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

      因此我们知道了AbstractStringBuilder能做到动态追加字符的原理在于不断扩容和追加新字符。至此我们解释完了AbstractStringBuilder的主要功能的实现原理,下面我们看看StringBuilder的相关实现:

    public final class StringBuilder
        extends AbstractStringBuilder
        implements java.io.Serializable, CharSequence
    {
        ...
        public StringBuilder() {
            super(16);
        }
        public StringBuilder(int capacity) {
            super(capacity);
        }
    
        public StringBuilder(String str) {
            super(str.length() + 16);
            append(str);
        }
    
        @Override
        public StringBuilder append(String str) {
            super.append(str);
            return this;
        }
    
        ...
    }
    

      通过上述代码,我们可以看到:StringBuilder的初始化和动态追加字符的功能都是依赖于它的父类AbstractStringBuilder,因此,StringBuilder也能像AbstractStringBuilder一样动态追加字符。但是需要注意的一点是:StringBuilderappend()方法是线程不安全的,在高并发的情况下,有可能会出现意想不到的结果。因此在高并发的情况下,我们更需要线程安全的操作字符序列的类,当然,JDK为我们提供了这样一个线程安全的类,这个类就是StringBuffer

    StringBuffer

       StringBufferStringBuilder一样都是继承自AbstractStringBuilder,其常用方法的实现如下:

    public final class StringBuffer
        extends AbstractStringBuilder
        implements java.io.Serializable, CharSequence
    {
        ...
    
        /**
         * A cache of the last value returned by toString. Cleared
         * whenever the StringBuffer is modified.
         */
        private transient char[] toStringCache;
    
        public StringBuffer() {
            super(16);
        }
    
        public StringBuffer(int capacity) {
            super(capacity);
        }
    
        public StringBuffer(String str) {
            super(str.length() + 16);
            append(str);
        }
    
        ...
    
        @Override
        public synchronized String toString() {
            if (toStringCache == null) {
                toStringCache = Arrays.copyOfRange(value, 0, count);
            }
            return new String(toStringCache, true);
        }
    
        ...
    
        @Override
        public synchronized StringBuffer append(String str) {
            toStringCache = null;
            super.append(str);
            return this;
        }
    }
    

       StringBuffer定义了一个私有的transient类型的变量toStringCache,该变量只是在最终返回字符串的时候用于缓存,其他时候一旦当前对象进行改变,该数组都会被置为null,方便垃圾回收。
       StringBuffer实现同步的原理很简单,就是将AbstractStringBuilder的一些append方法以synchronized同步的方式进行了重写。因此, StringBuffer在追加内容的时候可以保证线程安全,但是由于加了同步机制,每次追加的时候都必须先获取锁,然后再追加字符,然后再释放锁,因此其性能会大打折扣。所以在追求高性能和不要求线程安全的情况下,我们推荐使用StringBuilder来创建动态的字符串,强烈不推荐字符串String+的方式进行追加。

    总结

    • String的内容是不可变的。
    • StringBuilder提供了一种线程不安全的动态字符串修改的实现方式,但是效率较高。
    • StringBuffer提供了一种线程安全的动态字符串修改的实现方式,但是效率相对较低,每次追加都需要不断地获取锁和释放锁。

    相关文章

      网友评论

          本文标题:String, StringBuilder,StringBuff

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