StringBuffer 和 StringBuilder

在实际的项目开发过程中,可能我们更多时候是在操作可变的字符串,例如,动态的拼接SQL语句。

之所以叫StringBuilder和StringBuffer为可变字符串是因为:

(1)提供了大量改变原字符串的操作,如append()方法。而改变是在原来字符串的基础上修改的,并不像String中的一些方法,通过new String()来返回一个新的字符串。

(2)字符串会自动扩容,不需要人为的进行操作,而String本身是不进行扩容的。 

1、两者的类头:

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

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

首先看接口为相同的:java.io.Serializable 和 CharSequence

说明两者都可被序列化 和 需要注意:CharSequence就是字符序列,String, StringBuilder和StringBuffer本质上都是通过字符数组实现的!

2、源码方法

再看父类,都是 AbstractStringBuilder,在看下AbstractStringBuilder的,Appendable是纯接口里面只是几中append抽象方法,没有实现。AbstractStringBuilder它的实现类中的重写方法都是调用super.xxx(),所以只用看它源码就行。

(1)、append(String str)

abstract class AbstractStringBuilder implements Appendable, CharSequence {
    char[] value; 
    int count;        //长度
    AbstractStringBuilder() {}  //无参构造
    AbstractStringBuilder(int capacity) {  //初始化一个长度为capacity的char[]
        value = new char[capacity];
    }

    //下面是append的最主要的
    public AbstractStringBuilder append(String str) {
        //填一个空的
        if (str == null) return appendNull();
        //添加的字符串的长度
        int len = str.length();
        //进行扩容,就是原来的增加2倍+2(不绝对),最后面会有他们的源码解析
        ensureCapacityInternal(count + len);
        //从0到len的str全部长度拷贝到value[]从count位置开始,也就是从结尾开始拷贝str的全部进行追加
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }
}

 有许多的append()方法,处理方法都非常类似,有兴趣的可以自己去看。append()方法只能在末尾进行追加,如果要对字符串中间进行插入,则可以使用insert()方法,它也有很多方法,举个例子:

(2)、insert(int offset,String str)

    //offset要拷贝的开始位置就是原本的结尾位置,str为追加的串
    public AbstractStringBuilder insert(int offset, String str) {
        if ((offset < 0) || (offset > length()))
            throw new StringIndexOutOfBoundsException(offset);
        if (str == null)
            str = "null";
        int len = str.length();
        ensureCapacityInternal(count + len);//扩容
        System.arraycopy(value, offset, value, offset + len, count - offset);// 从value中的指定位置向后腾出len个位置
        str.getChars(value, offset); // 将str中包含的字符插入value的指定位置
        count += len;
        return this;
    }

可以看到,都会进行自动扩容,然后将str字符串中的字符插入到value中的指定位置。由于会自动扩容,所以在使用可变的字符串时,最好指定足够的容量,以防止进行扩容。因为底层需要进行数组的拷贝。

测试程序如下:

StringBuilder str =new StringBuilder("abc");
str.insert(1, "xx");
System.out.println(str.toString());// axxbc

3、同步问题:

StringBuilder和StringBuffer类都实现了这个抽象类,只是StringBuffer类中公开的方法都加上了synchronized关键字进行同步,也是与StringBuilder的最大不同。
例如:StringBuffer,它的每个方法都加了synchronized,保证线程安全

@Override
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }

但是对于StringBuffer的序列化与反序列化时,只有writeObject加了synchronized,而readObject跟StringBuilder的readObject一样没有加synchronized。

    private synchronized void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
        java.io.ObjectOutputStream.PutField fields = s.putFields();
        fields.put("value", value);
        fields.put("count", count);
        fields.put("shared", false);
        s.writeFields();
    }

    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        java.io.ObjectInputStream.GetField fields = s.readFields();
        value = (char[])fields.get("value", null);
        count = fields.get("count", 0);
    }


CharSequence是个接口,表示字符序列,而String、StringBuilder和StringBuffer都实现这个接口,底层都是通过数组来实现的。

4、String、StringBuilder、StringBuffer三个类进行比较:

异同点:

  1. String的对象是不可变的;而StringBuilder和StringBuffer是可变的。
  2. StringBuilder不是线程安全的;而StringBuffer是线程安全的
  3. String中的offset,value,count都是被final修饰的不可修改的;而StringBuffer和StringBuilder中的value,count都是继承自AbstractStringBuilder类的,没有被final修饰,说明他们在运行期间是可修改的,而且没有offset变量。

相同点:
三个类都是被final修饰的,是不可被继承的。且都能表示表示字符序列(源代码中可以看出都继承了CharSequence.java类 )

5、其他源码:

(1)、getChars()源码:

实际是它里面的System.arraycopy达到了进行追加的功能

    ////从srcBegin到srcEnd的str全部长度拷贝到dst[]从dstBegin位置开始,也就是从结尾开始拷贝str的全部进行追加
    public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)
    {
        if (srcBegin < 0)
            throw new StringIndexOutOfBoundsException(srcBegin);
        if ((srcEnd < 0) || (srcEnd > count))
            throw new StringIndexOutOfBoundsException(srcEnd);
        if (srcBegin > srcEnd)
            throw new StringIndexOutOfBoundsException("srcBegin > srcEnd");
        //下面这个方法是native方法,实现数组之间的复制,value表示源数组,srcBegin表示源数组要复制的起始位置,dst表示目标数组,dstBegin为目标数组其实位置,length表示从目标复制到源数组的长度。
        System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
    }

(2)、拷贝System.arraycopy的函数原型是(native方法):

public native static void arraycopy(Object src,
                             int srcPos,
                             Object dest,
                             int destPos,
                             int length)

其中:src表示源数组,srcPos表示源数组要复制的起始位置,desc表示目标数组,length表示要复制的长度。

 (3)、扩容ensureCapacityInternal()源码:

Integer.MAX_VALUE为啥要减8,数组对象的形状和结构(如int值数组)与标准Java对象类似。主要区别在于数组对象有一个额外的元数据,用于表示数组的大小。这个8就是元数据大小。

    //Integer.MAX_VALUE是int的最大值(2^31),减8是减去存储数组的大小。
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    
    //扩容的函数,就是原来的value不变,只是将长度增加,原本字符串的长度增加为原来的2倍+2
    private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0) {
            //第一个参数是,源数组,第二个参数是要拷贝到源数组的长度
            value = Arrays.copyOf(value,newCapacity(minimumCapacity));
        }
    }
    
    private int newCapacity(int minCapacity) {
        // 原本字符串的长度安位左移1位,相当于*2
        int newCapacity = (value.length << 1) + 2;
        //如果加进来的容量比原本字符串的2倍+2还要大的话,就变为大的。不过一般都是minCapacity=value.length+str.length,
        //所以如果str.length的长度要是大于value.length+2的话,就不会按照原本字符串+2进行长度的分配,而是按这个minCapacity分配。
        if (newCapacity - minCapacity < 0) {
            newCapacity = minCapacity;
        }
        return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
            ? hugeCapacity(minCapacity)
            : newCapacity;
    }
    //获取比MAX_ARRAY_SIZE大于等于的值,小于Integer.MAX_VALUE的值,只要不越界
    private int hugeCapacity(int minCapacity) {
         //Integer.MAX_VALUE是没减8的也就是int的最大值,如果他俩相减还小于0,说明越界
        if (Integer.MAX_VALUE - minCapacity < 0) {  overflow
            throw new OutOfMemoryError();
        }
        //除此之外,如果MAX_ARRAY_SIZE < minCapacity < Integer.MAX_VALUE就取它本身,要是小于MAX_ARRAY_SIZE 那就取MAX_ARRAY_SIZE
        return (minCapacity > MAX_ARRAY_SIZE)
            ? minCapacity : MAX_ARRAY_SIZE;
    }    

猜你喜欢

转载自blog.csdn.net/xiao1_1bing/article/details/81191216
今日推荐