• java中String,StringBuilder,StringBuffer实现原理,优化


    在java中字符串使我们编程时经常需要使用的类型,java中提供了String、StringBuilder、StringBuffer等相关字符串操作类。
    首先来看String,JDK中String的实现是一个final类,其内部主要还是一个char数组组成。
    public final class String
    implements java.io.Serializable, Comparable, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    }
    之所以要定义为final类型,是因为String的hash算法是和char数组相关的:

       public int hashCode() {
            int h = hash;
            if (h == 0 && value.length > 0) {
                char val[] = value;
    
                for (int i = 0; i < value.length; i++) {
                    h = 31 * h + val[i];
                }
                hash = h;
            }
            return h;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    并且String一旦初始化之后,后面就不能在对这个String实例的内容进行更新,只能生成一个新的。
    主要还是因为这里String的hashCode计算方式是和value数组相关的。是通过value数组计算得到的。如果我们修改了value值,那么String的hashCode就会发生改变,而JDK中很多实现都是依赖于hashCode的,比如map。我们开始放入了一个key为String的对象,如果我们修改value数组,那么后续我们无法获取到这个key对应的值了(map的key是需要hashCode运算的)。
    另外一个很经典的问题:

    String a = "Hello World";
            String b = new String("Hello World");
            System.out.println(a == b );
            System.out.println(a.equals(b));
            System.out.println(a == b.intern());
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这里会输出:

    false
    true
    true
    
    • 1
    • 2
    • 3

    首先需要明确的一点是:
    String a = "Hello World"; 这里的Hello World是一个字符串常量,这块内存布局如下:
    在这里插入图片描述
    这里的 String a = "Hello World"; 中 “Hello World”;这个是在常量池里面分配的,a的引用是直接指向常量池的。
    而b是在堆上分配的,b的引用是指向堆上的对象的的。
    而如果调用了String.inern方法,那么返回的是一个字符串常量池的对象,我们可以看看这个方法的定义:

    
        /**
         * Returns a canonical representation for the string object.
         * <p>
         * A pool of strings, initially empty, is maintained privately by the
         * class {@code String}.
         * <p>
         * When the intern method is invoked, if the pool already contains a
         * string equal to this {@code String} object as determined by
         * the {@link #equals(Object)} method, then the string from the pool is
         * returned. Otherwise, this {@code String} object is added to the
         * pool and a reference to this {@code String} object is returned.
         * <p>
         * It follows that for any two strings {@code s} and {@code t},
         * {@code s.intern() == t.intern()} is {@code true}
         * if and only if {@code s.equals(t)} is {@code true}.
         * <p>
         * All literal strings and string-valued constant expressions are
         * interned. String literals are defined in section 3.10.5 of the
         * <cite>The Java&trade; Language Specification</cite>.
         *
         * @return  a string that has the same contents as this string, but is
         *          guaranteed to be from a pool of unique strings.
         */
        public native String intern();
    ```
    可以看到intern方法返回是一个和当前String对象拥有相同内容的字符串常量池对象,如果字符串常量池没有会创建一个,如果已经有了,那么直接返回。
    ` String a = "Hello World";``这种方式分配的对象是直接在字符串常量池中的,与b.intern(0的常量池对象是同一个。
    
    
    #### StringBuilder
    StringBuilder相比String而言其实大体差不多,底层也是有一个char数组,但是其数组是可变的。
    ```
    public final class StringBuilder
        extends AbstractStringBuilder
        implements java.io.Serializable, CharSequence
    {
    .....
    }
    ```
    StringBuilder是继承自AbstractStringBuilder,StringBuffer也是,只不过相关方法StringBuffer增加了synchronized同步语义。
    StringBuilder默认初始容量是16,当我们使用append的时候,实现如下:
    ```java
    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;
        }
    private void ensureCapacityInternal(int minimumCapacity) {
            // overflow-conscious code
            if (minimumCapacity - value.length > 0) {
                value = Arrays.copyOf(value,
                        newCapacity(minimumCapacity));
            }
        }
    ```
    `ensureCapacityInternal`是确保底层char数组有足够大的容量来容纳字符内容,如果不够将进行扩容,通过`Arrays.copyOf()`对原数组进行扩容。
    `AbstractStringBuilder`中还有一个属性`count`,用来标记当前数组中有多少个元素被填充,coutn <= value.length
    
    这样,我们每次append数组的时候,如果容量不够都会进行扩容。
    
    而StringBuider的toString方法很简单粗暴:
    ```java
     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);
        }
    ```
    就是将当前StringBuilder的value数组复制到String的value数组中去。
    
    实际上如果我们再初始StringBuilder的时候可以将容量放大一点,这样可以避免在append过程中不断进行数组的扩容复制。
    另外一个就是StringBuilder提供了一个`setLength`方法:
    ```java
    public void setLength(int newLength) {
            if (newLength < 0)
                throw new StringIndexOutOfBoundsException(newLength);
            ensureCapacityInternal(newLength);
    
            if (count < newLength) {
                Arrays.fill(value, count, newLength, '\0');
            }
    
            count = newLength;
        }
    ```
    可以看到如果新的大小比已有的内容大小大,那么会进行扩容并填充``\0\,如果不是,那么直接重设` count = newLength;`
    如果我们`setLength(0)`那么原有分配的数组不会被回收内容不变,只是count=0,这样后续append的时候,就会覆盖之前的内容。
    
    #### StringBuffer
    StringBuffer基本上与StringBuilder一样,但是在append方法上会加上synchronized同步语义
    
    
    
    
    
    
    
    
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
  • 相关阅读:
    浅谈 volatile
    『LeetCode|每日一题』---->递增的三元子序列
    维视智造x西安电子科技大学,联合授课助力AI产业人才培养
    Anchor-free目标检测综述 -- Keypoint-based篇
    Fourier变换中的能量积分及其详细证明过程
    WebShell 木马免杀过WAF
    StarUML6.0.1使用
    面试算法23:两个链表的第1个重合节点
    PoseiSwap的趋势性如何体现?
    Scala011--Scala中的常用集合函数及操作Ⅱ
  • 原文地址:https://blog.csdn.net/LeoHan163/article/details/125505775