• StringBuilder的底层实现原理


    StringBuilder是我们常用来动态拼接字符串的一个类,通常在面试中被问到这个可能就离不开这个问题“String、StringBuffer和StringBuilder的区别?”。这个问题也是老生常谈的,相信大家都不陌生。一个最明显的区别就是String是不可变的,在动态拼接字符串时会产生很多新的对象,StringBuffer和StringBuilder就是用来解决这个问题的,它们继承了 AbstractStringBuilder ,底层基于可修改的char数组(JDK8),而这两个的区别是StringBuffer加了synchronized关键字,是线程安全的,而StringBuilder是非线程安全的。

    知道它们的区别,那么StringBuilder的底层到底是如何实现修改的呢?分析部分源码如下:

    1、StringBuilder的结构
    public final class StringBuilder extends AbstractStringBuilder implements java.io.Serializable, CharSequence{
    	....
    }
    
    ~~~~~~~~~~~~~~~~~~~~~~~萌萌哒分割线~~~~~~~~~~~~~~~~~~~~~~~~~~
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    abstract class AbstractStringBuilder implements Appendable, CharSequence {
        /**
         * The value is used for character storage.
         */
        char[] value;
    
        /**
         * The count is the number of characters used.
         */
        int count;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    可以看到StringBuilder继承自AbstractStringBuilder,底层有一个char数组存放字符,count记录字符个数。

    题外话看过String源码的这里也可以看出StringBuilder和String的区别,String类被final修饰不可被继承,而且String类底层存放字符的char数组也是被final修饰不可修改的

    2、创建StringBuilder对象,看下它的构造函数
    	/**
         * Constructs a string builder with no characters in it and an
         * initial capacity of 16 characters.
         */
        public StringBuilder() {
            super(16);
        }
    
        /**
         * Constructs a string builder with no characters in it and an
         * initial capacity specified by the {@code capacity} argument.
         *
         * @param      capacity  the initial capacity.
         * @throws     NegativeArraySizeException  if the {@code capacity}
         *               argument is less than {@code 0}.
         */
        public StringBuilder(int capacity) {
            super(capacity);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    StringBuilder构造函数默认调用的父类方法,代码如下:

        /**
         * Creates an AbstractStringBuilder of the specified capacity.
         */
        AbstractStringBuilder(int capacity) {
            value = new char[capacity];
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    可以看到当我们使用new StringBuilder(),其实就是创建了一个大小默认为16的数组,也可以手动指定数组的大小。

    3、再看下StringBuilder的拼接方法append,部分源码如下:
    	@Override
        public StringBuilder append(String str) {
            super.append(str);
            return this;
        }
    
    ~~~~~~~~~~~~~~~~~萌萌哒分割线~~~~~~~~~~~~~~~~~~~~~
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    	父类AbstractStringBuilder的appeng方法实现:
    	
    	/**
         * Appends the specified string to this character sequence.
         * 

    * The characters of the {@code String} argument are appended, in * order, increasing the length of this sequence by the length of the * argument. If {@code str} is {@code null}, then the four * characters {@code "null"} are appended. *

    * Let n be the length of this character sequence just prior to * execution of the {@code append} method. Then the character at * index k in the new character sequence is equal to the character * at index k in the old character sequence, if k is less * than n; otherwise, it is equal to the character at index * k-n in the argument {@code str}. * * @param str a string. * @return a reference to this object. */ public AbstractStringBuilder append(String str) { if (str == null) return appendNull();//@1 int len = str.length();//@2 ensureCapacityInternal(count + len);//@3 str.getChars(0, len, value, count);//@4 count += len;@5 return this;//@6 }

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    逐个分析上面的@1~@6,看看具体每步做了什么。
    @1:appentNull()

    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;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    这里是当我们append一个null时自动往数组加上null这四个字符,然后返回当前对象。里面有个ensureCapacityInternal(c + 4);后面再看

    @2:int len = str.length();待添加的字符串长度
    @3:ensureCapacityInternal(count + len);将当前char数组中字符个数加上待添加的字符串长度,也调用的这个方法和拼接null时一样,只不过拼接null时待添加的字符串长度等于4,看下这个方法做了什么

    /**
         * For positive values of {@code minimumCapacity}, this method
         * behaves like {@code ensureCapacity}, however it is never
         * synchronized.
         * If {@code minimumCapacity} is non positive due to numeric
         * overflow, this method throws {@code OutOfMemoryError}.
         */
        private void ensureCapacityInternal(int minimumCapacity) {
            // overflow-conscious code
            if (minimumCapacity - value.length > 0) {
                value = Arrays.copyOf(value,
                        newCapacity(minimumCapacity));
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    看代码结合方法注释就很清楚,就是将字符总数和char数组的长度比对,确保char数组长度是否足够,如果不够就需要扩容,copy一个新的数组,将旧数组的数据复制到新数组中,然后将引用赋值给value,同时这里newCapacity里面对于新数组的长度也有一些判断逻辑,源码如下:

        /**
         * Returns a capacity at least as large as the given minimum capacity.
         * Returns the current capacity increased by the same amount + 2 if
         * that suffices.
         * Will not return a capacity greater than {@code MAX_ARRAY_SIZE}
         * unless the given minimum capacity is greater than that.
         *
         * @param  minCapacity the desired minimum capacity
         * @throws OutOfMemoryError if minCapacity is less than zero or
         *         greater than Integer.MAX_VALUE
         */
        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;
        }
    
        private int hugeCapacity(int minCapacity) {
            if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
                throw new OutOfMemoryError();
            }
            return (minCapacity > MAX_ARRAY_SIZE)
                ? minCapacity : MAX_ARRAY_SIZE;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    首先按原来char数组大小的2倍+2作为扩容新数组的容量大小,如果这个容量还是小于拼接后字符串的总长度,就按拼接后字符串的总长度作为扩容新数组的容量大小,这只是初步估算的大小
    接下来还要判断MAX_ARRAY_SIZE与这个容量的大小,以及拼接后字符串的总长度与MAX_VALUE的大小,扩容后数组的大小最大为MAX_ARRAY_SIZE=Integer.MAX_VALUE - 8

    @4:str.getChars(0, len, value, count);将待添加的字符添加到char数组
    @5:count += len;将count记录的字符个数加上新加的
    @6:return this;返回当前对象

    4、总结

    通过上面的分析总结下StringBuilder的底层实现原理:
    (1)new StringBuilder()默认创建一个容量大小=16的char数组
    (2)调用append方法拼接字符串首先会判断char数组容量是否组足够,如果不够需要扩容,按原来数组大小的2倍+2扩容,将原来char数组的元素赋值到新扩容的数组中,并将新数组引用赋值给原来的value,
    (3)将待拼接的字符也添加到新数组中,将记录字符个数的count更新
    (4)返回当前对象

    分析StringBuilder其他更多的方法会发现每次操作返回的都是当前对象,这也是为什么在动态拼接字符串的时候推荐使用它而非String的原因。另一个点就是由于 StringBuilder 底层是基于 char 数组存放字符,而数组是连续内存结构,为了防止频繁地复制和申请内存,在预先知道字符串大概长度的情况下,new StringBuilder()时可以先指定capacity的值,减少数组的扩容次数提高效率。

  • 相关阅读:
    cache2go-源码阅读
    ROS action客户端和服务端通信(Ubuntu )
    通讯录(纯C语言实现)
    Redis应用场景及常见的数据类型
    SpringCloud Gateway 服务网关的快速入门
    MapStruct_概念、如何使用、子集和映射、合并、Spring方式、表达式、自定义切面处理
    ORA-01940 无法删除当前已连接的用户之解决方案(脚本)
    为什么UI自动化难做?—— 关于Selenium UI自动化的思考
    项目介绍,项目架构和微服务划分
    AI 大框架分析基于python之TensorFlow(归一化处理,多类别分类的概率)
  • 原文地址:https://blog.csdn.net/u013614857/article/details/128172171