深入源码分析StringBuffer和StringBuilder

深入源码分析StringBuffer和StringBuilder

众所周知,StringBuffer是线程安全,StringBuilder线程不安全,所以StringBuilder性能略高,那还有没有其他细节上的特性呢?让我们从源码分析

StringBuffer和StringBuilder都继承了AbstractStringBuilder类

AbstractStringBuilder类关键源码

  • 抽象类,是Stringbuilder和StringBuffer的父类,有两个主要的属性:
    • 字符数组value,用来存放字符串
    • int类型的count值,用于计算存储使用的字符数量,并且每次增删改都会更新该count值
    • capacity方法才是返回字符数组的总长度,value.length;
  • 该类中的大部分方法被其子类StringBuffer和StringBuilder所调用,其中的扩容机制是关键
abstract class AbstractStringBuilder implements Appendable, CharSequence {
    //用来存字符串
    char[] value;
    //用来计算存储使用的字符数量
    int count;
    public int length() { 
        //注意,这里的length方法返回的是count的值,而不是value.length,
        //区别于String类中的length方法 
        return count;  
    }  
   
    //capacity()才是返回字符数组的总长度
    public int capacity() {
        return value.length;
    }

ensureCapacity扩容机制

    //扩容机制 确保容量
     public void ensureCapacity(int minimumCapacity) {
        // 最小容量肯定大于0,严谨性判断
        if (minimumCapacity > 0)
            // 一层一层环环相扣的封装
            ensureCapacityInternal(minimumCapacity);
    }
    
    private void ensureCapacityInternal(int minimumCapacity) {
        //如果最小容量大于内置的数组长度就必须要扩容了
        if (minimumCapacity - value.length > 0)
            expandCapacity(minimumCapacity);
    }
    //开始扩容
    void expandCapacity(int minimumCapacity) {
        //自动扩容机制,每次扩容(value.length+1)*2 
        int newCapacity = value.length * 2 + 2; 
        //如果扩容后的容量还是小于传入的参数,就直接将传入的参数设为容量值
        if (newCapacity - minimumCapacity < 0)
            newCapacity = minimumCapacity;
        //如果传入的参数小于0,则直接把容量设置到Integer的最大值 2^31-1 
        if (newCapacity < 0) {
            if (minimumCapacity < 0) // overflow
                throw new OutOfMemoryError();
            newCapacity = Integer.MAX_VALUE;
        }
        //还是使用了Arrays.copyOf函数直接返回一个新容量的数组
        value = Arrays.copyOf(value, newCapacity);
    }
    
    //用于保留value的值,保留的长度为count的值,只有当count的值小于value.length时起作用
    public void trimToSize() {
        //当count小于value.length时,将value多余长度的值删除,
        //此时value.length的长度等于count
        if (count < value.length) {
            value = Arrays.copyOf(value, count);
        }
    }
    // 设置长度length
    public void setLength(int newLength) {
        if (newLength < 0)
            throw new StringIndexOutOfBoundsException(newLength);
        //当传入的值大于等于0时,进行扩容
        ensureCapacityInternal(newLength);
        
		//当传入值大于字符统计量
        if (count < newLength) { 
            //为新扩容的数组中的新元素填充'\0',同样也是结束符 从count位置到newLength位置
            Arrays.fill(value, count, newLength, '\0');  
        }
        //最后设置新的字符长度量
        count = newLength;
    }
    
    //append方法使用到的一个方法,用以添加一个字符串数组
    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");
        //用于添加字符串,将value的值添加到dst[]中  
        System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
    }

append方法

    //append方法的重载
    //在对象后拼接字符串或其他对象,效率相比String较高,
    //因其在拼接时并没有创建新的对象,不会造成大量对象的堆积,性能提升相当明显
    public AbstractStringBuilder append(Object obj) {
       	return append(String.valueOf(obj));
    }
    
    public AbstractStringBuilder append(String str) {
        if (str == null)
            //若传入的字符串为null,则默认添加“null”这个字符串
            return appendNull();
       
        int len = str.length();
        //注意扩容,直接是计算字符量的长度,根据长度就扩容到这么多
        ensureCapacityInternal(count + len);
        //用getChars方法去添加字符串数组
        str.getChars(0, len, value, count);
        //更新count字符量值,每次添加就会加上新加入的字符串长度值
        count += len;
        return this;
    }
    
     //拼接StringBuffer对象,方法同理
     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;
    }
    //同样也可以拼接AbstractStringBuilder对象,也可以是其实现类,例如StringBuilder
     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;
    }
    
     //对象为null时,就添加个“null”字符串
     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;
    }
    
     //添加int类型的值
     public AbstractStringBuilder append(int i) {
        //细节,当int值是最小值时,特殊处理
        if (i == Integer.MIN_VALUE) {
            append("-2147483648");
            return this;
        }
        //细节,判断Integer的位数,负数有负号,要多加一位
        int appendedLength = (i < 0) ? Integer.stringSize(-i) + 1
                                     : Integer.stringSize(i);
        //根据新传入的int值,确定总体字符串长度值,根据此时长度值进行扩容
        int spaceNeeded = count + appendedLength;
        //扩容
        ensureCapacityInternal(spaceNeeded);
        //扩容之后就是添加值了,int值用Integer的静态方法
        Integer.getChars(i, spaceNeeded, value);
        //更新总体的字符数组长度值
        count = spaceNeeded;
        return this;
    }
    //添加long类型的值,原理和int类似
    public AbstractStringBuilder append(long l) {
        if (l == Long.MIN_VALUE) {
            append("-9223372036854775808");
            return this;
        }
        int appendedLength = (l < 0) ? Long.stringSize(-l) + 1
                                     : Long.stringSize(l);
        int spaceNeeded = count + appendedLength;
        ensureCapacityInternal(spaceNeeded);
        Long.getChars(l, spaceNeeded, value);
        count = spaceNeeded;
        return this;
    }
   	//浮点类型,直接调用对应的包装类中的方法
    public AbstractStringBuilder append(float f) {
        FloatingDecimal.appendTo(f,this);
        return this;
    }
    
     public AbstractStringBuilder append(double d) {
        FloatingDecimal.appendTo(d,this);
        return this;
    }
    
    //添加Boolean类型 简单粗暴
    public AbstractStringBuilder append(boolean b) {
        if (b) {
            ensureCapacityInternal(count + 4);
            value[count++] = 't';
            value[count++] = 'r';
            value[count++] = 'u';
            value[count++] = 'e';
        } else {
            ensureCapacityInternal(count + 5);
            value[count++] = 'f';
            value[count++] = 'a';
            value[count++] = 'l';
            value[count++] = 's';
            value[count++] = 'e';
        }
        return this;
    }

delete方法

    //删除一部分的字符,传入要删除段的开始下标和终止下标
    public AbstractStringBuilder delete(int start, int end) {
     	//验证参数的有效性  
        if (start < 0)
            throw new StringIndexOutOfBoundsException(start);
        if (end > count)
            //结束下标大于count时,将count设为结束下标 
            end = count; 
        if (start > end)
            //开始下标就大于结束下标
            throw new StringIndexOutOfBoundsException();
        //要删除的长度值
        int len = end - start;
        if (len > 0) {
            
            //System的静态方法来实现数组之间的复制。
            //src:源数组;
            //srcPos:源数组要复制的起始位置;
            //dest:目的数组;
            //destPos:目的数组放置的起始位置;
            //length:复制的长度。注意:src and dest都必须是同类型或者可以进行转换类型的数组.
            
        	//System.arraycopy函数可以实现自己到自己复制
            //将中间要删除段省略,不进行复制 
            System.arraycopy(value, start+len, value, start, count-end);
            //更新count大小 
            count -= len;
        }
        return this;
    }

insert方法

    //在对象中间插入字符串数组
    public AbstractStringBuilder insert(int dstOffset, CharSequence s) {
  
        if (s == null)
            s = "null";
        //明确了是String类型,会发生强制转换
        if (s instanceof String)
            return this.insert(dstOffset, (String)s);
        return this.insert(dstOffset, s, 0, s.length());
    }
    
    //传入的是插入开始下标,以及要插入的字符串
    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);//先扩容
        //再复制数组,注意,此时只是在value中建立起用于存放插入值的空位   
        System.arraycopy(value, offset, value, offset + len, count - offset);
        //向空位中插入str
        str.getChars(value, offset);
        //更新count值
        count += len;  
        return this;
    }

reverse方法

    //反转字符串
    public AbstractStringBuilder reverse() {
        boolean hasSurrogates = false;
        int n = count - 1;
        //采用从中间向两端遍历,对换对称位置上的字符  
        for (int j = (n-1) >> 1; j >= 0; j--) {
            int k = n - j;
            char cj = value[j];//两个暂存变量
            char ck = value[k];
            value[j] = ck;//直接对应位置交换
            value[k] = cj;
            //验证每个字符的编码是否在范围内
            if (Character.isSurrogate(cj) ||
                Character.isSurrogate(ck)) {
                hasSurrogates = true;
            }
        }
        if (hasSurrogates) {
        	//直接反转后,如果两字符顺序错了,就需要重新调整。
        	//考虑到存在增补字符,需要成对校验,就是超出了字符的编码范围的话就会重新翻转到原来那样
            reverseAllValidSurrogatePairs();
        }
        return this;
    }
    
    //reverse的依赖方法,重新调整字符顺序
    private void reverseAllValidSurrogatePairs() {
        for (int i = 0; i < count - 1; i++) {
            char c2 = value[i];
            if (Character.isLowSurrogate(c2)) {
                char c1 = value[i + 1];
                if (Character.isHighSurrogate(c1)) {
                    value[i++] = c1;
                    value[i] = c2;
                }
            }
        }
    }

substring方法

    //返回一个新字符串,是此字符串的一个子字符串
    public String substring(int start, int end) {
        if (start < 0)
            throw new StringIndexOutOfBoundsException(start);
        if (end > count)
            throw new StringIndexOutOfBoundsException(end);
        if (start > end)
            throw new StringIndexOutOfBoundsException(end - start);
        //注意是返回了一个新创建的字符串对象
        return new String(value, start, end - start);
    }
}

System.arraycopy

Arrays.copyOf()方法返回的数组是新的数组对象,数组拷贝时调用的是本地方法 System.arraycopy() ,原数组对象仍是原数组对象,不变,该拷贝不会影响原来的数组。

System.arraycopy() 源码如下:

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

参数说明:
src:源对象
srcPos:源数组中的起始位置
dest:目标数组对象
destPos:目标数据中的起始位置
length:要拷贝的数组元素的数量

StringBuilder的关键源码

  • 线程不安全,修改的方法全部都为线程不安全,是牺牲了安全用以实现性能
  • 由final修饰,不能被继承,并且继承了AbstractStringBuilder类
  • 重写了toString方法
  • 实现了序列化接口,可序列化
  • 默认初始化容量为capacity = 16 ,基本所有jdk中实现类涉及初始化容量的大小都为16,加上一点扩容机制
public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence{
	 //对象序列化和反序列化需要的唯一标识版本号
     static final long serialVersionUID = 4383685877147921099L;
     //默认构造方法
     public StringBuilder() {
      //使用父类的构造方法,默认初始化容量为capacity = 16 
             super(16);
      }
     //带一个参数的构造方法,可以指定初始化容量
     public StringBuilder(int capacity) {
        super(capacity);
      }
     //带一个参数构造方法,与前一个不同,这是指定一个String来初始化
     public StringBuffer(String str) {
        //注意,String初始化StringBuffer的时候,指定容量大小为String的长度加上16
        super(str.length() + 16);
        //追加到value中
        append(str);
    }
  
    
    @Override
    public StringBuilder append(Object obj) {
        return append(String.valueOf(obj));
    }

    @Override
    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }
    @Override
    public String toString() {
        // Create a copy, don't share the array
        // 注意,此时是new了一个String对象,返回了该String对象
        return new String(value, 0, count);
    }
    //将对象序列化,写入了count和value。
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
        s.defaultWriteObject();
        s.writeInt(count);
        s.writeObject(value);
    }
    
    //用于反序列化,将count和value属性读取出来
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();
        count = s.readInt();
        value = (char[]) s.readObject();
    }
}

StringBuffer的关键源码

一个字符序列可变的字符串,这个StringBuffer提供了一系列的修改方法去改变字符串对象序列

  • 线程安全,增删改操作方法都加了synchronized锁,加锁系统开销大,效率相对StringBuilder较低
  • 注意:和Builder的区别有一个transient char[] toStringCache,toStringCache的字符数组,最后一次修改后的缓存值(字符数组保存),只要修改了value,那么就会重置,当然这个Cache是建立在线程安全之上的,不保证线程安全就不会涉及到该Cache的操作,该toStringCache会在使用toString方法时起到提高效率的作用
 public final class StringBuffer
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
{
    // 注意:此属性Builder中没有,意图是最后一次修改后的缓存值(字符数组保存),
    // 只要修改了value,那么就会重置
    // transient 用于指定哪个字段不被默认序列化
    private transient char[] toStringCache;
    
    static final long serialVersionUID = 3388685877147921107L;
 
     public StringBuffer() {
        super(16);
    }
     public StringBuffer(int capacity) {
        super(capacity);
    }
    // 获取字符串字符数量,加synchronized锁
    @Override
    public synchronized int length() {
        return count;
    }
    // 获取容量,效率低--对象锁
    @Override
    public synchronized int capacity() {
        return value.length;
    }
    // 确保容量
    @Override
    public synchronized void ensureCapacity(int minimumCapacity) {
        if (minimumCapacity > value.length) {
        // 当最小容量值(传进来的参数值)大于value.length(这个其实就是容量),那么就直接扩容
            expandCapacity(minimumCapacity);
        }
    }
    //扩容,和AbstractStringBuilder父类中方法一致
    void expandCapacity(int minimumCapacity) {
        int newCapacity = value.length * 2 + 2;
        if (newCapacity - minimumCapacity < 0)
            newCapacity = minimumCapacity;
        if (newCapacity < 0) {
            if (minimumCapacity < 0) 
                throw new OutOfMemoryError();
            newCapacity = Integer.MAX_VALUE;
        }
        value = Arrays.copyOf(value, newCapacity);
    }
  
    @Override
    public synchronized void trimToSize() {
    	//直接调用父类的方法,但线程安全
        super.trimToSize();
    }
    //也是线程安全
    @Override
    public synchronized void setLength(int newLength) {
        toStringCache = null;
         //调用父类函数,扩充字符串容量到newLength,并且用空格填充
        super.setLength(newLength);
    }
    //根据指定索引获取字符,效率慢,加锁
    @Override
    public synchronized char charAt(int index) {
        if ((index < 0) || (index >= count))
            throw new StringIndexOutOfBoundsException(index);
        return value[index];
    }
    
     //根据索引修改字符串中某个字符值 
    @Override
    public synchronized void setCharAt(int index, char ch) {
        if ((index < 0) || (index >= count))
            throw new StringIndexOutOfBoundsException(index);
        //注意清除了缓存,只要修改了value,此值就会置空   
        toStringCache = null;
        value[index] = ch;
    }
    
    //区别在于加锁了以及清除了缓存,只要修改了value,此值就会置空   
    @Override
    public synchronized StringBuffer append(Object obj) { 	
        toStringCache = null;
        super.append(String.valueOf(obj));
        return this;
    }
    @Override
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }
    
    
    //不存在内存泄漏,实现了线程安全 
    @Override
    public synchronized String substring(int start) {
        return substring(start, count);
    }
    
    @Override
    public synchronized String substring(int start, int end) {
        return super.substring(start, end);
    }
    

    //有一套这样的insert方法,分为同步与不同步方法
    //只有在参数为char类型时,才是同步方法
    @Override
    public  StringBuffer insert(int offset, boolean b) {
    	//此方法不同步, 而且也没有 toStringCache = null;
        super.insert(offset, b);
        return this;
    }
    @Override
    public synchronized StringBuffer insert(int offset, char c) {
        toStringCache = null;
        super.insert(offset, c);
        return this;
    }
    

    @Override
    public synchronized String toString() {
    	//toStringCache是提高toString方法的效率,不用每次都是调用,做了一个缓存
        if (toStringCache == null) {
            toStringCache = Arrays.copyOfRange(value, 0, count);
        }
        //直接使用的toStringCache数组缓存,提高了效率
        return new String(toStringCache, true);//返回一个新的string对象过去
    }
    
    // 自定义序列化字段
    //serialPersistentFields 用于指定哪些字段需要被默认序列化.如下:
    private static final java.io.ObjectStreamField[] serialPersistentFields =
    {
        new java.io.ObjectStreamField("value", char[].class),
        new java.io.ObjectStreamField("count", Integer.TYPE),
        new java.io.ObjectStreamField("shared", Boolean.TYPE),
    };

    // 序列化大到ObjectOutputStream,写入了count和value、shared
    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);//默认shared为false
        s.writeFields();
    }

    //反序列化到对象,读出count和value
    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);
    }

}

字符串系列类的一些问题

效率问题

效率:StringBuilder > StringBuffer > String

  • StringBuffer和StringBuilder都有自动扩容机制,增删改操作时,返回的是原来自身的对象,不会重新创建对象,不会产生很多对象垃圾
  • String有两大缺点:
    • 返回对象使用大量new操作,返回值均为新建的String对象,会在堆内存中产生很多垃圾;
    • 虽然最终调用的是系统复制数组操作,但调用之前开销非常大,只能靠复制来解决拼接问题
 public static void main(String[] args) {
        long start = System.currentTimeMillis();
        String str = null;
        for (int i = 0; i < 20000; i++) {
            str = str + i + ",";
        }
        System.out.println("String耗时  "+ (System.currentTimeMillis() - start));
        System.out.println("-------------------");


        start = System.currentTimeMillis();
        StringBuffer buffer = new StringBuffer();
        for (int i = 0; i < 20000; i++) {
            buffer.append(i + ",");//线程安全所以慢一点,但前提这里是单线程
        }
        System.out.println("StringBuffer耗时  "+(System.currentTimeMillis() - start));
        System.out.println("-------------------");
        start = System.currentTimeMillis();
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < 20000; i++) {
            builder.append(i + ",");//线程不安全,效率最高
        }
        System.out.println("StringBuilder耗时  "+(System.currentTimeMillis() - start));
    }
/*    
String耗时 2030
-------------------
StringBuffer耗时 5
-------------------
StringBuilder耗时 3
*

线程安全问题

  • 增删改操作时,StringBuffer线程安全,StringBuilder线程不安全

使用总结

  1. 如果要操作少量的String数据可以使用用 String

  2. 不考虑线程安全问题,大量数据进行操作使用 StringBuilder

  3. 考虑线程安全问题,大量数据进行操作使用 StringBuffer

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值