StringBuilder/StringBuffer可变长原理


1. 概述

  • StringBufferStringBuilder 都是可变字符序列(字符串)。
  • 不同的是 StringBuilder 非线程安全,StringBuffer 线程安全。
  • 查看源码可以发现,StringBuffer 除构造外的其他方法都直接或间接(重载)用 synchronized 修饰。
  • 这里主要讨论可变长,也就是扩容原理,以 StringBuilder 为例,StringBuffer 与之一致。

2. StringBuilder 的构造方法

StringBuilder构造方法

// 类定义
public final class StringBuilder
   extends AbstractStringBuilder
   implements java.io.Serializable, CharSequence
{
   // StringBuilder 的构造
   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);
   }
   ...
   ...
}
  • 4 种构造方法都使用了父类 AbstractStringBuilder 的构造器。
  • 除了第二种构造外,其他构造种都带有醒目的 16 ,换算后正好是 2 4 2^4 24,感觉没啥联系,emm。
  • 无参构造,默认传参 16。
  • 参数为 int 类型的构造,传参就为指定的 int 值。
  • 参数为 String 类型的构造,传参为 String 的长度 + 16。
  • 参数为 CharSequence 类型的构造,调用的是参数为 int 的构造,最终传参为 CharSequence 的长度 + 16。

3. AbstractStringBuilder 构造方法

abstract class AbstractStringBuilder implements Appendable, CharSequence {
   /* The value is used for character storage. */ 
   /*  value 用于存放字符 */
   char[] value;
   /* The count is the number of characters used. */ 
   /* count 是有效字符个数 */
   int count;
   
   AbstractStringBuilder() {
   }
   
   AbstractStringBuilder(int capacity) {
       value = new char[capacity];
   }
   ...
   ...
}
  • AbstractStringBuilder 有两个构造方法重载,一个无参构造,一个带 int 类型参数的构造。
  • 前面 StringBuilder 中都使用的是带参构造。
  • 看到 new char[capacity],心里一下就踏实了,与 String 一样,底层都是 char 数组,长度就是前面传递过来的长度,不同的是这里没有使用 final 关键字修饰。

对于 StringBuilder 的初始化结论如下:

  • 对于无参构造,初始大小为: 16
  • 对于传入 int 类型的构造,初始大小为:指定的 int 值
  • 对于传入 String 类型和 CharSequence 类型的构造,初始大小为:字符序列长度 + 16

4. 扩容机制

  • 添加新字符时可能会导致原数组溢出,这时需要对原数组进行扩容,确保新的数组能够容纳新添加的字符序列。
  • 扩容是发生在添加操作前的动作,所以不得不提 append() 方法,如下图:
    在这里插入图片描述
  • 有许多重载方法,针对不同类型做不同处理,都大同小异,这里使用添加 String 类型作为示例,定义如下:
@Override
public StringBuilder append(String str) {
   super.append(str);
   return this;
}
  • 这里重写了父类的方法,但真正调用的,还是父类中的方法,定义如下:
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;
}
  • 先处理了 String 为 null 的情况,调用的是 appendNull() 方法,
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;
}
  • 可以发现,不管是 append() 还是 appendNull() 方法,里面都有一个方法 ensureCapacityInternal(),且传递的参数都是 count + 字符长度,这个就是用于扩容的方法,它内部的实现如下:
private void ensureCapacityInternal(int minimumCapacity) {
   // overflow-conscious code
   if (minimumCapacity - value.length > 0) {
       value = Arrays.copyOf(value,
               newCapacity(minimumCapacity));
   }
}
  • 参数 minimumCapacity 就是 count + 待添加字符序列的长度,如果这个值比原数组的长度小,说明还未存满,不做任何处理。
  • 反之则需要扩容了,可以看到扩容操作是基于 Arrays.copyOf() 实现的,新数组的长度由 newCapacity() 获取,在进入这个方法之前,需要先对参数 minimumCapacity 做进一步的解释。
  • 前面提到 minimumCapacitycount + 待添加字符序列的长度关于 count,它在 AbstractStringBuilder 中的有声明,表示的是字符数组 value 中有效字符的个数,即已存储字符的个数。
  • 令人费解的是,基本类型必须初始化才能使用,但我未能找到 count 初始化相关的代码,只好将它作为默认值 0 处理。【已解决】
  • 【基本类型作为成员变量时,Java 会自动为其设置默认值】
  • 知道 minimumCapacity 的参数的含义后,继续看 newCapacity(minimumCapacity) 方法,它的定义如下:
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;
}
  • 进入方法的第一件事就是定义新数组的大小 int newCapacity = (value.length << 1) + 2;
  • value.length<<1 等价于 value.length *= 2,所以扩容后数组的大小为 原数组的大小(value.length) * 2 + 2
  • 众所周知, int 类型指的是 10 进制的整数,但在计算机中,它是以二进制存储的。
  • 还记得初学 Java 时,老师教的 int 类型占 4 个字节大小吗?
  • 4 byte == 32 bit,所以 int 的取值范围 [ − 2 31 , 2 31 − 1 ] [-2^{31} ,{2^{31} - 1}] [231,2311]
  • 说了一堆,就是为了铺垫出 int 表面上是 10 进制,背地里却是二进制,可以将其当作二进制处理。
  • 所以 value.length<<1 是将整体像高位移动了一位,就如同十进制的 10 变成了 100,扩大的 10 倍,同理二进制的 0010 变成 0100,扩大了 2 倍,由此得出每次扩容后的大小为 原数组的长度 * 2 + 2

5. 总结

1、 StringBuilder 或 StringBuffer 的初始化分配的空间大小取决于调用的构造方法:

  • 无参构造默认大小为 16
  • 调用 int 类型参数构造,初始化大小为指定的 int 值。(更推荐这种方式,可以减少扩容次数,提高效率。)
  • 调用 String 类型或 CharSequence 类型参数的构造,初始化大小为:字符序列的长度 + 16

2、扩容机制每次扩容大小为:原数组大小 * 2 + 2


3、补充:

  • StringBuilder 调用 length() 方法时,返回值为有效字符个数,它的源码如下:
@Override
public int length() {
   return count;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值