stringbuilder_有趣的JDK、StringBuilder家族三巨头

AbstractStringBuilder

是一个可变的字符序列类的抽象类,最初出现于jdk1.5中。

历史:

众所周知java最常用的字符数据类型是Stirng对象,但Stirng对象被设计为了一个常量类(内部的维护 final char[]),每次变更都会产生新的对象。在需要频繁更换字符值的场景下,很浪费系统资源。为了解决这个问题可变字符的对象(StringBuilder)家族诞生了。(StringBuffer+StringBuilder) 而 AbstractStringBuilder现在是他们的爸爸。

为什么加粗了"现在"这个词呢?

因最先出现的是StringBuffer 它诞生于jdk.10中(灵异事件这个时候他现在的爸爸还没出生),他是一个线程安全类。后来我们聪明的java开发团队发现实际应用中大多数场景下是不需要线程安全的

所以在jdk1.5中 小爸爸AbstractStringBuilder(封装了一些公用方法)和小弟弟StringBuilder(非线程安全类)诞生了

类图

9d89ac8b489a6e2ae78745e17a68f163.png

StringBuilder家族三巨头

abstract class AbstractStringBuilder implements Appendable, CharSequence { char[] value; int count;}

AbstractStringBuilder 实现了两个接口。我们简单介绍下他的两个干爹

  • CharSequence 一个可读序列类接口,string也实现了他。我们可以理解为他规范了java中字符类型对象的基础方法 (int length(); char charAt(int index); public String toString(); 等)
  • Appendable 可追加的字符序列类接口,我们可以理解为 CharSequence规范了字符操作,Appendable规范了字符追加操作 ,截止jdk8中它之规定了 append方法和他的重载方法

核心成员

char[] value;

这个很好理解用于存储字符类型的 value数组

int count;

这个用来表示 char[] 字符长度。问题来了:为什么不用char数组的value.length来表示字符长度呢?带着疑问我们往下看

常用方法

 /** * 追加字符串 str **/ 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; } /** * char数组扩容 * @param minimumCapacity 期望的最小数组长度 */ private void ensureCapacityInternal(int minimumCapacity) { if (minimumCapacity - value.length > 0) { // 当前长度小于最小期望 进行扩容 value = Arrays.copyOf( value, newCapacity(minimumCapacity) // 新的数组长度由 newCapacity方法产生 ); // 产生新的数组 } }  /** * @param minCapacity 最小期望长度 */ private int newCapacity(int minCapacity) { // overflow-conscious code // 先取当前数组长度 乘2在加2, “为了减少扩容次”数每次扩容长度最起码都要翻倍(不然每次都扩容的话不久和String一样了)。 // 为什么是成“二再加二”? 不知道!有人知道么? // jdk注解很少告诉我为什么,但应该是经过思考的选择. 有兴趣可以深究一下 int newCapacity = (value.length << 1) + 2; if (newCapacity - minCapacity < 0) { newCapacity = minCapacity; // 上一步操作后还不满足 最小期望,那就使用最小期望值来作为新的数组长度 } // 当新长度, 小于零或者 大于数组最大长度(MAX_ARRAY_SIZE)的时候,交给hugeCapacity方法选择新数组长度 // MAX_ARRAY_SIZE 有意思了 MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; // 为什么最大长度等于 int最大长度 - 8 呢? // java中外定义数组时传入数组长度参数类型为int所以无法定义超过Integer.MAX_VALUE长度的数组(编译时就会报错) // 那为什么要 - 8 呢 ? // jdk注释是这么写的: // Some VMs reserve some header words in an array. // Attempts to allocate larger arrays may result in // OutOfMemoryError // 一些虚拟机在数组中保留一些空间,尝试分配较大的数组可能会导致内存溢出错误! 所以减了个8。 // 不过注意 当程序尝试分配的大小 在 MAX_ARRAY_SIZE 》 Integer.MAX_VALUE 之间时 hugeCapacity方法还是会返回期望值。否则返回 MAX_ARRAY_SIZE 或者抛出异常 return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0) ? hugeCapacity(minCapacity) : newCapacity; }  /** * 最小期望长度 大于 Integer.MAX_VALUE 最大值时抛出异常 * @param minCapacity 最小期望长度 * @return 返回 MAX_ARRAY_SIZE(数组最大长度) 或者 返回minCapacity( minCapacity > MAX_ARRAY_SIZE) */ private int hugeCapacity(int minCapacity) { if (Integer.MAX_VALUE - minCapacity < 0) { // overflow throw new OutOfMemoryError(); } return (minCapacity > MAX_ARRAY_SIZE) ? minCapacity : MAX_ARRAY_SIZE; }  /** * 这个方法还是留着子类自己去实现了 */ @Override public abstract String toString();

解释大部分在注释里 这里系统的整理一下

整理下上面的问题

为什么不用char数组的value.length来表示字符长度呢?

看过上面的源码以及注释可以知道 char[] 每次扩容都是由newCapacity方法决定的。这里有个机制为了防止每次操作字符串都要扩容char数组,每次扩容时都尝试多开辟些空闲空间。下次更改字符时会先用空闲空间 不够用在扩容 所以数组长度大于等于实际内容长度,那么这里需要单独一个字段记录实际内容长度了

一个引申问题我们常用的 HotSpot java虚拟机中数组最大长度到底是多少?

理论上数组最大长度应该是2^31

  • 二进制31位最大值
  • 虽然java规定数组对象头信息中用于记录数组长度的空间大小为 32bit, 但是。java中int大小为4字节32位,其中有一位表示正负号。所以int正整数最大值是2^31

事实上HotSpot1.8 下数组最大长度是 (2^31 - 1), 有兴趣的朋友可以自行实验

  • 就像注释里说的一些虚拟机在数组中保留一些空间,尝试分配较大的数组可能会导致内存溢出错误

定期发布技术类 原创作品,欢迎小伙伴们,转发,关注,收藏哦~~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值