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

Posted Leo Han

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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;
    

并且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());

这里会输出:

false
true
true

首先需要明确的一点是:
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的时候,实现如下:

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方法很简单粗暴:

 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方法:

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同步语义

以上是关于java中String,StringBuilder,StringBuffer实现原理,优化的主要内容,如果未能解决你的问题,请参考以下文章

JAVA辨析:String,StringBuffer与StringBuilder

Java之String+StringBuffer+StringBuilder

java中String&StringBuffer&StringBuilder

重温java中的String,StringBuffer,StringBuilder类

Java中String, StringBuffer, StringBuilder

java中String StringBuilder 和 StringBuffer的联系与区别