有趣的JDK、StringBuilder家族源码

AbstractStringBuilder

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

历史

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

  • 因最先出现的是StringBuffer 它诞生于jdk.10中(灵异事件这个时候他现在的爸爸还没出生),他是一个线程安全类。后来我们聪明的java开发团队发现实际应用中大多数场景下是不需要线程安全的
  • 所以在jdk1.5中 小爸爸AbstractStringBuilder(封装了一些公用方法)和小弟弟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虚拟机中数组最大长度到底是多少?

执行这段代码

   public static void main(String[] args) {
        int i = Integer.MAX_VALUE;
        while (true) {
            try {
                System.out.println(new char[i].length);
            } catch (OutOfMemoryError e) {
                i--;
                e.printStackTrace();
                // 异常继续
            }
            System.out.println("数组最大长度 十进制:" + i);
            System.out.println("数组最大长度 二进制:" + Integer.toBinaryString(i));
            System.out.println("数组最大长度 二进制位数:" + Integer.toBinaryString(i).length());
            return;
        }
    }

会输入什么样的内容呢?

java.lang.OutOfMemoryError: Requested array size exceeds VM limit
	at com.tlong.TestAbstracString.main(TestAbstracString.java:10)
数组最大长度 十进制:2147483646
数组最大长度 二进制:1111111111111111111111111111110
数组最大长度 二进制位数:31

理论上数组最大长度应该是2^31
  • 二进制31位最大值
  • 虽然java规定数组对象头信息中用于记录数组长度的空间大小为 32bit, 但是。java中int大小为4字节32位,其中有一位表示正负号。所以int正整数最大值是2^31
事实上HotSpot1.8 下数组最大长度是 (2^31 - 1)
  • 就像注释里说的一些虚拟机在数组中保留一些空间,尝试分配较大的数组可能会导致内存溢出错误
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值