【Java基础】分析StringBuilder与StringBuffer的扩容机制

简介

  • StringBuffer是Java1.0的API,StringBuilder是Java1.5的API
  • StringBuffer和StringBuilder都是继承自AbstractStringBuilder
    在这里插入图片描述
  • 区别在于StringBuffer是线程安全的,StringBuilder是线程不安全的,前者安全但效率低,后者高效但不安全,它们的扩容机制也是这样的区别

所以我们只需要分析一个的扩容就可以了,分析StringBuffer,另一个只用把synchronized关键字去掉就是一样的,本文是基于Java1.8源码讲解的,与1.7略有不同,但原理是一致的

String 与 CharSequence区别

在这里插入图片描述

  • String 继承于CharSequence,也就是说String也是CharSequence类型。
  • CharSequence是一个接口,它只包括length(), charAt(int index), subSequence(int start, int end)这几个API接口。
  • 除了String实现了CharSequence之外,StringBuffer和StringBuilder也实现了CharSequence接口。
  • CharSequence就是字符串,String, StringBuilder和StringBuffer本质上都是通过字符数组实现的!
  • CharSequence与String都能用于定义字符串,但CharSequence的值是可读可写字符串,而String的值是只读字符串

正文

  1. StringBuffer构造方法(初始容量)
    既然是容器就一定会有个初始容量的,目的在于避免在内存中过度占用内存.容器的初始容量有默认和使用构造方法申明两种.
方法 说明
StringBuffer() 构造一个其中不带字符的字符串缓冲区,其初始长度16个字符。
StringBuffer(CharSequence seq) 构造一个字符串缓冲区,它包含与指定的 CharSequence 相同的字符。
StringBuffer(int capacity) 构造一个不带字符,但具有指定初始长度的字符串缓冲区。
StringBuffer(String str) 构造一个字符串缓冲区,并将其内容初始化为指定的字符串内容。其初始长度为 当前字符长度 + 16
  1. 不声明长度,使用无参构造方法创建实例对象时,会调用父类的构造方法,默认长度为16

    //StringBuffer部分源码,StringBuffer继承了AbstractStringBuilder 
    public final class StringBuffer extends AbstractStringBuilder implements java.io.Serializable, CharSequence{
     	public StringBuffer() {
        		super(16);//调用父类构造方法
     	}
    } 	
    
    //父类AbstractStringBuilder部分源码
    abstract class AbstractStringBuilder implements Appendable, CharSequence {
    	AbstractStringBuilder(int capacity) {//父类构造方法
        	value = new char[capacity];
    	 }
    	 //...........
    }
    

    通过上面源码可以看出,默认容量实际上就是创建了一个长度16的字符数组;

  2. 使用有参构方法传入字符串,初始化实例对象时,初始长度为当前字符串长度 + 16

    //StringBuffer部分源码
    public StringBuffer(int capacity) {
        super(capacity);//调用父类构造方法
    }
    public StringBuffer(String str) {
        super(str.length() + 16);//调用父类构造方法
        append(str);
    }
    public StringBuffer(CharSequence seq) {
        this(seq.length() + 16);
        append(seq);
    }
    

    可以看出有参构造方法有3个

    • 直接声明长度的,底层是根据传入长度来创建了一个字符数组,
    • 使用字符串的创建呢,底层的数组长度是字符串长度+16
    • 如果是用字符序列那么和字符串(字符串长度+16)是一样的,然后执行append操作.
  3. StringBuffer的原理
    StringBuffer本质上是一个可变字符数组,不像String,String是一个 final修饰的char[] 的不可变字符数组
    并且它继承了抽象类AbstractStringBuilder,在AbstractStringBuilder类中,有`三个字段

    char[] value;// 保存字符
    int count;// 已有字符长度
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;// 最大数组大小
    

    value表示存储字符count表示数组中已有内容的长度MAX_ARRAY_SIZE表示最大的分配容量,即2^31-8,若分配长度超过最大容量将会报OutOfMemoryError的错误:请求的数组大小超过VM限制


    StringBuffer的主要操作有append、insert等,这些操作都是在value上进行的,而不是像String一样每次操作都要new一个String,因此,StringBuffer在效率上要高于String。有了append、insert等操作,value的大小就会改变,那么StringBuffer是如何操作容量的改变的呢?
    首先StringBuffer有个继承自AbstractStringBuilder类ensureCapacity的方法:
    StringBuffer对其进行了重写,通过super直接调用父类的expandCapacity方法。

     //StringBuffer方法
    @Override
    public synchronized void ensureCapacity(int minimumCapacity) {
        super.ensureCapacity(minimumCapacity);
    }
    
    //AbstractStringBuilder部分源码
    @Native public static final int   MAX_VALUE = 0x7fffffff;
    
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    
    //如果传入的`minimunCapacity`大于 0
    public void ensureCapacity(int minimumCapacity) {
        if (minimumCapacity > 0)
            ensureCapacityInternal(minimumCapacity);
    }
    
    //如果传入的`minimunCapacity`比原来`value`的长度大,就会调用`newCapacity()`
    private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0) {
        //通过Arrays.copyOf(原数组,新长度)返回一个新长度数组  如果长度超过原数组的长度,则保留数组默认值
            value = Arrays.copyOf(value,
                    newCapacity(minimumCapacity));
        }
    }
    
    private int newCapacity(int minCapacity) {
        // overflow-conscious code
        //将新容量扩大到value的长度乘以2加2  (1*value^2)+ 2
        int newCapacity = (value.length << 1) + 2;
        //新容量大小 小于 传入容量 minCapacity ,将新容量变成实际需要容量大小
        if (newCapacity - minCapacity < 0) {
            newCapacity = minCapacity;
        }
        
        //如果不小于
        //如果新容量大小<=0 或者 最大容量大小 小于 新容量大小,则创建新容量
        return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
            ? hugeCapacity(minCapacity)
            : newCapacity;
    }
    
    private int hugeCapacity(int minCapacity) {
    	//如果最大容量大小 小于 实际需要容量大小,抛出OOM
        if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
            throw new OutOfMemoryError();
        }
        //如果不小于
        //如果实际需要容量大小 大于最大容量大小则返回 实际需要容量大,否则返回最大容量大小
        return (minCapacity > MAX_ARRAY_SIZE)
            ? minCapacity : MAX_ARRAY_SIZE;
    }
    

    扩容策略(2*n + 2)
    先将新容量扩大到value的长度乘以2加2,如果不够则直接扩充到所需的容量大小,够则还要进行判断。如果比Integer的最大值还要大会抛异常;否则,如果比MAX_ARRAY_SIZE大,则创建新容量为minCapacity;如果比MAX_ARRAY_SIZE小,则新容量为MAX_ARRAY_SIZ

public static void main(String[] args) {
        //1:调用无参数构造器
        StringBuffer str = new StringBuffer();
        str.append("12345");
        System.out.println(str.capacity());//16
        System.out.println(str.length());//5
        str.append("67890123456");
        System.out.println(str.capacity());//16
        System.out.println(str.length());//16
        str.append("1");
        System.out.println(str.capacity());//34
        System.out.println(str.length());//17
        //2:调用有参数构造器
        str = new StringBuffer("123");
        System.out.println(str.capacity());//19
        System.out.println(str.length());//3
    }
发布了62 篇原创文章 · 获赞 109 · 访问量 5292

猜你喜欢

转载自blog.csdn.net/qq877728715/article/details/103075647
今日推荐