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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值