美文网首页Java
String及StringTable(四):StringBuff

String及StringTable(四):StringBuff

作者: 冬天里的懒喵 | 来源:发表于2020-08-12 18:16 被阅读0次

    分析完StringBuilder,然后再聊StringBuffer就简单多了。因为StringBuffer同样也是继承了AbstractStringBuilder。

    1.类结构及成员变量

    1.1 类结构

    image.png

    此时对为啥要将StringBuilder还拆分为一个抽象类AbstractStringBuilder恍然大悟了。因为StringBuffer可以复用。

    /**
     * A thread-safe, mutable sequence of characters.
     * A string buffer is like a {@link String}, but can be modified. At any
     * point in time it contains some particular sequence of characters, but
     * the length and content of the sequence can be changed through certain
     * method calls.
     * <p>
     * String buffers are safe for use by multiple threads. The methods
     * are synchronized where necessary so that all the operations on any
     * particular instance behave as if they occur in some serial order
     * that is consistent with the order of the method calls made by each of
     * the individual threads involved.
     * <p>
     * The principal operations on a {@code StringBuffer} are the
     * {@code append} and {@code insert} methods, which are
     * overloaded so as to accept data of any type. Each effectively
     * converts a given datum to a string and then appends or inserts the
     * characters of that string to the string buffer. The
     * {@code append} method always adds these characters at the end
     * of the buffer; the {@code insert} method adds the characters at
     * a specified point.
     * <p>
     * For example, if {@code z} refers to a string buffer object
     * whose current contents are {@code "start"}, then
     * the method call {@code z.append("le")} would cause the string
     * buffer to contain {@code "startle"}, whereas
     * {@code z.insert(4, "le")} would alter the string buffer to
     * contain {@code "starlet"}.
     * <p>
     * In general, if sb refers to an instance of a {@code StringBuffer},
     * then {@code sb.append(x)} has the same effect as
     * {@code sb.insert(sb.length(), x)}.
     * <p>
     * Whenever an operation occurs involving a source sequence (such as
     * appending or inserting from a source sequence), this class synchronizes
     * only on the string buffer performing the operation, not on the source.
     * Note that while {@code StringBuffer} is designed to be safe to use
     * concurrently from multiple threads, if the constructor or the
     * {@code append} or {@code insert} operation is passed a source sequence
     * that is shared across threads, the calling code must ensure
     * that the operation has a consistent and unchanging view of the source
     * sequence for the duration of the operation.
     * This could be satisfied by the caller holding a lock during the
     * operation's call, by using an immutable source sequence, or by not
     * sharing the source sequence across threads.
     * <p>
     * Every string buffer has a capacity. As long as the length of the
     * character sequence contained in the string buffer does not exceed
     * the capacity, it is not necessary to allocate a new internal
     * buffer array. If the internal buffer overflows, it is
     * automatically made larger.
     * <p>
     * Unless otherwise noted, passing a {@code null} argument to a constructor
     * or method in this class will cause a {@link NullPointerException} to be
     * thrown.
     * <p>
     * As of  release JDK 5, this class has been supplemented with an equivalent
     * class designed for use by a single thread, {@link StringBuilder}.  The
     * {@code StringBuilder} class should generally be used in preference to
     * this one, as it supports all of the same operations but it is faster, as
     * it performs no synchronization.
     *
     * @author      Arthur van Hoff
     * @see     java.lang.StringBuilder
     * @see     java.lang.String
     * @since   JDK1.0
     */
     public final class StringBuffer
        extends AbstractStringBuilder
        implements java.io.Serializable, CharSequence
    {
    
    }
    

    其注释大意为:提供一个线程安全,可变的字符串序列。这个字符串序列可以通过一些方法进行调整。
    StringBuffer对于对现场而言是安全的,因为内部所有的方法都是同步方法,通过synchronized确保了这些方法的顺序性。主要的一些操作是append和insert等方法。每当涉及到对StringBuffer源序列进行同步操作的时候,这个类仅仅执行字符缓冲区,而不是源对象。
    每个StringBuffer有一个容量,只要长度不超过buffer的容量,没必要重新分配。一旦长度大于内部buffer长度,则会自动放大。
    将null传递给构造函数会抛出NullPointerException。
    StringBuilder与之等效,但是其效率更优,因为它不同步。

    1.2 成员变量

    与StringBuilder很大的不同是,在StringBuffer中,存在一个toStringCache属性。

        /**
         * A cache of the last value returned by toString. Cleared
         * whenever the StringBuffer is modified.
         */
        private transient char[] toStringCache;
    
        /** use serialVersionUID from JDK 1.0.2 for interoperability */
        static final long serialVersionUID = 3388685877147921107L;
    
    

    这个成员变量的意思是说在toString需要的时候,将最新的value中的内容。如果有修改的时候则将这个cache清空。

    2.toString方法比较

    我们在前面知道,StringBuffer比StringBuilder多了一个toStringCache数组,为什么StringBuffer的实现与StringBuilder不同呢?我们对其代码进行分析。

    2.1 StringBuilder.toString

    @Override
    public String toString() {
        // Create a copy, don't share the array
        return new String(value, 0, count);
    }
    
    public String(char value[], int offset, int count) {
        if (offset < 0) {
            throw new StringIndexOutOfBoundsException(offset);
        }
        if (count <= 0) {
            if (count < 0) {
                throw new StringIndexOutOfBoundsException(count);
            }
            if (offset <= value.length) {
                this.value = "".value;
                return;
            }
        }
        // Note: offset or count might be near -1>>>1.
        if (offset > value.length - count) {
            throw new StringIndexOutOfBoundsException(offset + count);
        }
        this.value = Arrays.copyOfRange(value, offset, offset+count);
    }
    

    实际上这个还是使用的是 Arrays.copyOfRange。其底层是通过System.arraycopy进行的对象拷贝。

    2.2 StringBuffer.toString

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

    在看看String的这个构造方法:

    /*
    * Package private constructor which shares value array for speed.
    * this constructor is always expected to be called with share==true.
    * a separate constructor is needed because we already have a public
    * String(char[]) constructor that makes a copy of the given char[].
    */
    String(char[] value, boolean share) {
        // assert share : "unshared not supported";
        this.value = value;
    }
    

    这个构造方法只提供了同package中的类才能访问到。
    我们可以看到,实际上是一开始,在String类中定义了一个char数组,之后调用这个String比较特殊的构造函数,直接修改指针,指向这个共享的数组。
    最开始我也没看明白,这个toStringCache的意义。
    但是,我们试想一种情况,在多线程的情况下,如果使用了StringBuffer,且其值不发生改变的时候,就可以直接复用前一次的结果。虽然多个线程new了不同的String,但是其本质的char数组只有一个,不会重复的System.arraycopy。因此会带来性能的提升。虽然StringBuffer的toString方法本身是synchronized的,但是这样一来可以降低对象拷贝带来的性能损耗。

    3.同步

    StringBuffer与StringBuilder最大的区别在于同步,可以简单认为StringBuffer就是加上了synchronized的StringBuilder。实际上StringBuffer的实现也是在各个方法上加上synchronized关键字。

        @Override
        public synchronized int length() {
            return count;
        }
    
        @Override
        public synchronized int capacity() {
            return value.length;
        }
    
    
        @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);
        }
        
            public synchronized StringBuffer append(StringBuffer sb) {
            toStringCache = null;
            super.append(sb);
            return this;
        }
    
        /**
         * @since 1.8
         */
        @Override
        synchronized StringBuffer append(AbstractStringBuilder asb) {
            toStringCache = null;
            super.append(asb);
            return this;
        }
    
    

    可以看到所有核心的方法都是加上synchronized之后调用的super方法。
    在对String中char数组有结构性变更的地方都会将toStringCache变为null。

    4.总结

    StringBuffer与StringBuilder的主要区别在于:

    • StringBuffer是线程安全的,适合在多线程环境下使用,StringBuilder不是线程安全的,只能在单线程下使用。
    • 二者的toString方法实现不同,StringBuffer中考虑到了多线程环境,因而进行了优化,加入了toStringCache,当StringBuffer固定不变时多线程进行范围的时候可以有效复用。不会反复调用System.arraycopy。但是这个优化对于StringBuilder实际上意义不大,StringBuilder本身不具有线程安全性。尤其是对于toString方法,一定不要混淆其使用场景。可以参考一次简单却致命的错误。该文章说明了一次由于错误使用toString方法而带来的问题。

    相关文章

      网友评论

        本文标题:String及StringTable(四):StringBuff

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