JDK源码学习--StringBuilder类

上文我们介绍过JDK源码学习–String类,该类的内部用了一个char数组表示一个字符串对象的,只是该字符数组被final修饰,初始化之后就不能被修改,但是对于经常做字符串修改操作的情况下,String类就需要不断创建新对象,性能极低。StringBuilder内部也是封装的一个字符数组,只不过该数组非final修饰,可以不断修改。所以对于一些经常需要修改字符串的情况,我们应当首选StringBuilder。StringBuilder和StringBuffer内部代码几乎一样,StringBuffer的所有方法都被关键字synchronized修饰,它是线程安全的,但是线程安全是需要付出性能代价的,在实际使用中,适情况选择。


一、实现接口

StringBuilder的大部分方法中都会调用父类AbstractStringBuilder方法或属性,实现了Serializable和CharSequence接口。

public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence

在讲StringBuilder之前先简单介绍下它的父类AbstractStringBuilder,实现了Appendable和CharSequence接口

abstract class AbstractStringBuilder implements Appendable, CharSequence 

1、父类AbstractStringBuilder成员变量

该类有两个成员变量,value数组和String类中的char数组是一样的,只不过没有被final修饰,该数组内部的值是可以动态修改的,这也是StringBuilder存在的意义。count表示的是value数组中实际上存放的字符数目,例如:value长度为10,我存放8个字符,剩下位置为空,此时count的值就为8,而value.length()为10。

    //The value is used for character storage.
    char[] value;
    //The count is the number of characters used.
    int count;

2、父类AbstractStringBuilder构造函数

有参构造方法初始化容量,两个构造方法都不是public,被设计出来给子类使用的

    //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];
    }

3、父类AbstractStringBuilder常用方法

length()方法返回的是实际存放的字符数目,capacity()方法返回的是内置字符数组的长度。

public int length() {
    return count;
}
public int capacity() {
    return value.length;
}

ensureCapacity(int minimumCapacity)

保证字符数组长度的方法,对于一个StringBuilder对象,可以不断的添加字符串到其中,这样就会遇到value数组长度不够的时候,该方法就是用于处理这种情况。在我们实际操作value数组之前,大多会调用该方法判断此次操作之后是否会导致数组溢出,如果是则会将原数组长度扩大两倍加上2并拷贝原数组中的内容到新数组中,然后才实际操作value数组。

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

    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int newCapacity = (value.length << 1) + 2;
        if (newCapacity - minCapacity < 0) {
            newCapacity = minCapacity;
        }
        return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
            ? hugeCapacity(minCapacity)
            : newCapacity;
    }

    private int hugeCapacity(int minCapacity) {
        if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
            throw new OutOfMemoryError();
        }
        return (minCapacity > MAX_ARRAY_SIZE)
            ? minCapacity : MAX_ARRAY_SIZE;
    }

trimToSize()

去除value数组中所有为空的元素

public void trimToSize() {
   if (count < value.length) {
       value = Arrays.copyOf(value, count);
   }
}

append方法集

append这个方法是我们使用StringBuilder时最常用到的一个方法,该方法用于追加一个字符串到原StringBuilder对象的尾部。该方法接过来一个String对象,如果为null将会调用appendNull方法把字符串“null”追加到原对象的末尾,否则将会把该字符串追加到原对象的末尾。其他重载都是以各种各样的形式添加字符串到原StringBuilder对象的末尾,如果传入的是int,long,double,boolean等类型的参数,那么程序会将他们转换为字符串类型添加到末尾

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

    // Documentation in subclasses because of synchro difference
    public AbstractStringBuilder append(StringBuffer sb) {
        if (sb == null)
            return appendNull();
        int len = sb.length();
        ensureCapacityInternal(count + len);
        sb.getChars(0, len, value, count);
        count += len;
        return this;
    }

    /**
     * @since 1.8
     */
    AbstractStringBuilder append(AbstractStringBuilder asb) {
        if (asb == null)
            return appendNull();
        int len = asb.length();
        ensureCapacityInternal(count + len);
        asb.getChars(0, len, value, count);
        count += len;
        return this;
    }

    // Documentation in subclasses because of synchro difference
    @Override
    public AbstractStringBuilder append(CharSequence s) {
        if (s == null)
            return appendNull();
        if (s instanceof String)
            return this.append((String)s);
        if (s instanceof AbstractStringBuilder)
            return this.append((AbstractStringBuilder)s);

        return this.append(s, 0, s.length());
    }

    private AbstractStringBuilder appendNull() {
        int c = count;
        ensureCapacityInternal(c + 4);
        final char[] value = this.value;
        value[c++] = 'n';
        value[c++] = 'u';
        value[c++] = 'l';
        value[c++] = 'l';
        count = c;
        return this;
    }

二、StringBuilder构造方法:

StringBuilder因为它高度依赖他的父类,没有封装任何其他的属性,甚至没有封装字符数组,使用的是父类中封装的字符数组,包括他的构造函数也是调用的父类中的构造函数。这些构造函数会调用父类的一个构造函数为value字符数组初始化长度,如果没有显式传入需要设定的数组长度,则会默认为16。

	public StringBuilder() {
        super(16);
    }

    public StringBuilder(int capacity) {
        super(capacity);
    }

    public StringBuilder(String str) {
        super(str.length() + 16);
        append(str);
    }

    public StringBuilder(CharSequence seq) {
        this(seq.length() + 16);
        append(seq);
    }

三、StringBuilder常用方法

1、writeObject、readObject方法

writeObject(java.io.ObjectOutputStream s)在进行序列化的时候保存StringBuilder对象的状态到一个流中。
readObject(java.io.ObjectInputStream s)反序列化时从流中获取StringBuild对象序列化之前的状态。

 	private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
        s.defaultWriteObject();
        s.writeInt(count);
        s.writeObject(value);
    }

    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();
        count = s.readInt();
        value = (char[]) s.readObject();
    }

2、toString()

此方法重写了父类的toString()方法,创建了一个新的字符串对象,创建一个副本,而不要去共享这个内部维护的数组,因为返回的是String对象,不可变的,如果返回了数组的共享,在改变StringBuilder对象时,String对象的内容随之改变,这就破坏了String对象的不可变性。

	@Override
    public String toString() {
        // Create a copy, don't share the array
        return new String(value, 0, count);
    }

3、reverse()

此方法重写了父类的reverse(),调用父类的方法实现反转此字符串

	@Override
    public StringBuilder reverse() {
        super.reverse();
        return this;
    }

4、delete(int start, int end)

该方法重写了父类的delete方法,调用父类方法指定删除StringBuilder对象中指范围内的子串。该类中还有deleteCharAt(int index),根据角标删除字符,不再一一赘述。

	@Override
    public StringBuilder delete(int start, int end) {
        super.delete(start, end);
        return this;
    }
	//父类中的delete方法
 	public AbstractStringBuilder delete(int start, int end) {
        if (start < 0)
            throw new StringIndexOutOfBoundsException(start);
        if (end > count)
            end = count;
        if (start > end)
            throw new StringIndexOutOfBoundsException();
        int len = end - start;
        if (len > 0) {
            System.arraycopy(value, start+len, value, start, count-end);
            count -= len;
        }
        return this;
    }

5、insert(int index, char[] str, int offset, int len)

该方法接受四个参数,第一个参数表示要插入的索引位置,第二个参数表示要插入的字符数组或者字符串,第三个参数和第四个参数用于截取该原字符数组。核心方法依然是System.arraycopy,不过这里调用了两次,第一次的调用将index位置之后的所有字符往后移动len个长度(为即将插入的字符串留下空位置),第二次调用将该字符数组插入到预留位置,insert方法有很多重载,但是本质上大致相同,此处不再赘述。

	@Override
    public StringBuilder insert(int index, char[] str, int offset, int len)
    {
        super.insert(index, str, offset, len);
        return this;
    }

	//父类的方法
	public AbstractStringBuilder insert(int index, char[] str, int offset,
                                        int len)
    {
        if ((index < 0) || (index > length()))
            throw new StringIndexOutOfBoundsException(index);
        if ((offset < 0) || (len < 0) || (offset > str.length - len))
            throw new StringIndexOutOfBoundsException(
                "offset " + offset + ", len " + len + ", str.length "
                + str.length);
        ensureCapacityInternal(count + len);
        System.arraycopy(value, index, value, index + len, count - index);
        System.arraycopy(str, offset, value, index, len);
        count += len;
        return this;
    }

6、append方法

StringBuilder中的append方法都是重写了父类中的append方法:

@Override
public StringBuilder append(boolean b) {
    super.append(b);
    return this;
}

@Override
public StringBuilder append(char c) {
    super.append(c);
    return this;
}

@Override
public StringBuilder append(int i) {
    super.append(i);
    return this;
}

@Override
public StringBuilder append(long lng) {
    super.append(lng);
    return this;
}

@Override
public StringBuilder append(float f) {
    super.append(f);
    return this;
}

四、总结

1、String是Java中基础且重要的类,由于不可变性,在拼接字符串时候会产生很多无用的中间对象,如果频繁的进行这样的操作对性能有所影响。使用场景:在字符串不经常发生变化的业务场景优先使用String(代码更清晰简洁)。如常量的声明,少量的字符串操作(拼接,删除等)。

2、StringBuffer就是为了解决大量拼接字符串时产生很多中间对象问题,所有修改数据的方法都加上了synchronized,保证了线程安全,但是保证了线程安全是需要性能的代价的。使用场景:在多线程情况下,如有大量的字符串操作情况,应该使用StringBuffer。如HTTP参数解析和封装等。

3、StringBuilder解决了大量拼接字符串时产生很多中间对象问题,并且字符串拼接操作不需要线程安全。StringBuilder和StringBuffer他们其实和String差不多,内部一样都是封装的字符数组,只不过StringBuilder实现了动态扩容机制,可以动态扩容并且可以动态更改value数组中的元素而已,但本质上都是一样的。在单线程情况下,如有大量的字符串操作情况,应该使用StringBuilder来操作字符串。使用场景:不能使用String"+"来拼接而是使用,避免产生大量无用的中间对象,耗费空间且执行效率低下(新建对象、回收对象花费大量时间)。如JSON的封装等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值