• jdk源码阅读笔记-AbstractStringBuilder


      AbstractStringBuilder 在java.lang 包中,是一个抽象类,实现 Appendable 接口和 CharSequence 接口,这个类的诞生是为了解决 String 类在创建对象的时候总是创建新的对象的问题的。AbstractStringBuilder 有两个子类,分别是 StringBuilder 和 StringBuffer,这两个类的区别将会在下面说到。

      从源码中可以看出来,AbstractStringBuilder类和String类是非常相似的,设计的思想可以说是一模一样,其内部维护的都是一个char类的数组。唯一的区别在于这个数组的修饰符不一样,String类维护的是一个不可变的数组,而后者维护的则是一个可变的数组。

      AbstractStringBuilder源码:

    String类源码:

      AbstractStringBuilder 的应用场景

      该类主要适用于在短时间内创建大量字符串的场景,比如在一个循环体中需要拼接一个非常长的字符串时,可以考虑 AbstractStringBuilder 的实现类来创建字符串,这样不管这个字符串有多长最终都只会生成一个对象,但是如果在循环体中使用String来创建字符串时,此时会创建出大量的对象,这样不仅影响性能,同时如果内存较小的情况下很容易发生oom异常。下面列举一下使用的建议:

    public class Test {
    
        public static void main(String[] args) {
            /**
             * 不建议这样做:
             * 耗时:30726
             */
            String str = "";
            long start = System.currentTimeMillis();
            for (int i = 0; i < 100000; i++) {
                str += i;
            }
            long end = System.currentTimeMillis();
            System.out.println("耗费时间:" + (end - start));
            /**
             * 推荐做法:
             * 耗时:2
             */
            start = System.currentTimeMillis();
            StringBuilder builder = new StringBuilder();
            for (int i = 0; i < 100000; i++) {
                builder.append(i);
            }
         str = builder.tostring(); end
    = System.currentTimeMillis(); System.out.println("耗费时间:" + (end - start)); } }

      上面的代码中分别将 100000 拼接成一个字符串,记录所需时间,总共运行了三次,第一种直接使用String来拼接三次耗时均在3000毫秒以上,第二种使用StringBuilder方式拼接字符串时三次最高的一次才为15毫秒,通过对比发现使用StringBuilder比String直接拼接性能高了非常多。

      虽然在拼接大字符串是StringBuilder比String有着明显的优势,但是这并不意味这StringBuilder可以完全替代String,比如在创建比较小的字符串时使用String来创建就足够了,如果还是继续使用StringBuilder来创建,这样反而会对程序的性能有一定的影响,毕竟StringBuilder是一个对象,大量使用的话也会占内存。所以在使用字符串的时候要考虑清楚那种方式比较合理,这样才有助于提高程序的速度。

      AbstractStringBuilder 常用api

      AbstractStringBuilder的 常用api跟String常用的api几乎是一样的,而且其内部的实现方法也可以说是大同小异,所以就不再这里赘述了,只要了解了System类的静态方法 arraycopy(Object src, int srcPos,Object dest, int destPos,int length) 即可。里面的几乎所有的方法都用到了这个方法,是该类的核心。

      1.getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)

      该方法是将AbstractStringBuilder 的value数组 srcBegin 位置开始 srcEnd - srcBegin 长度的数组 复制到 dst 数组 dstBegin 开始的位置中;

      

     public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)
        {
            if (srcBegin < 0)
                throw new StringIndexOutOfBoundsException(srcBegin);
            if ((srcEnd < 0) || (srcEnd > count))
                throw new StringIndexOutOfBoundsException(srcEnd);
            if (srcBegin > srcEnd)
                throw new StringIndexOutOfBoundsException("srcBegin > srcEnd");
         //value: AbstractStringBuilder 中维护的 char类型数组 System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd
    - srcBegin); }

    大概流程是这样子的:

    第一次画图有点难看,将就着看。。。

      2、AbstractStringBuilder append(String str)

      该方法是在当前的字符串的后面添加字符串 str。该方法有非常多重载的方法,方法体大都一样,所以其他的就不一一说明了。

    public AbstractStringBuilder append(String str) {
            if (str == null)
                return appendNull();
            int len = str.length();
            //将value数组长度增加 len 个长度,以便后面将字符串放入到数组中
            ensureCapacityInternal(count + len);
            //将字符串 str 放入到 数组 value 中,从数组后面添加
            //count:当前 字符串长度
            str.getChars(0, len, value, count);
            //字符串长度增加 len
            count += len;
            return this;
        }

      3、public AbstractStringBuilder delete(int start, int end)

      该方法是从当前字符串中的 start位置开始删除字符串删到 end 位置;

     public AbstractStringBuilder delete(int start, int end) {
            /**
             * 删除边界检查
             */
            if (start < 0)
                throw new StringIndexOutOfBoundsException(start);
            if (end > count)
                end = count;
            if (start > end)
                throw new StringIndexOutOfBoundsException();
            //删除的长度
            int len = end - start;
            if (len > 0) {
                //注意:删除方法并不会重新创建一个新的数组,而是在原来的数组中将
                //需要删除的字符移动数组的最后面,然后将字符串的长度减少 len 个长度
                //从而达到删除的效果,这一点需要的注意,我在看代码的花了一点时间才弄明白
                System.arraycopy(value, start+len, value, start, count-end);
                //字符串长度减少 len 个长度
                count -= len;
            }
            return this;
        }

      举个例子,加入当前的字符串为 abcdefg , 然后执行删除操作,开始位置start为1,结束为止end为3,count为7,len为2,数组复制的大致流程如下:

    值得注意的是,在删除时数组长度并不会减少,这个跟我之前想的有很大的出入,减少的是count的值,即字符串的长度。

      4、public AbstractStringBuilder replace(int start, int end, String str)

      该方法是指当前字符串start 到 end 区间的字符串替换成 str 字符串。

     public AbstractStringBuilder replace(int start, int end, String str) {
            if (start < 0)
                throw new StringIndexOutOfBoundsException(start);
            if (start > count)
                throw new StringIndexOutOfBoundsException("start > length()");
            if (start > end)
                throw new StringIndexOutOfBoundsException("start > end");
    
            if (end > count)
                end = count;
            //替换字符串长度
            int len = str.length();
            //替换后当前字符串长度
            int newCount = count + len - (end - start);
            //根据替换后字符串长度调整 char 数组的大小,避免数组下标溢出异常
            ensureCapacityInternal(newCount);
            //对扩容后的char数组元素进行移动
            System.arraycopy(value, end, value, start + len, count - end);
            //将替换字符串添加到char数组中
            str.getChars(value, start);
            //修改有效字符串的长度
            count = newCount;
            return this;
        }

      大致的执行流程如下:

      加入当前的字符串为 abcdefg,start=1,end=3,替换字符串str为 ijk,则,

      第一步:计算出替换字符串后的长度:int newCount = 7 + 3 - (3-1)= 8;

      第二步:创建一个newCount 大小的数组,将原数组数据放入到新数组中,新数组多出来的元素默认为 null;

      上面两个步骤是在 ensureCapacityInternal(newCount) 方法中完成的。

      第三步:执行System.arraycopy(value, end, value, start + len, count - end)方法,该方法是将替换区间的后段数据移到最后面,顺序保持不变;

      第四步:将替换字符串放入到新的数组中;

      第五步:更新count 变量,该变量使用来记录字符串的长度。

      执行流程图:

      5、public AbstractStringBuilder insert(int index, char[] str, int offset,int len)

      该方法是用来向字符串中插入字符串,index:插入位置;str:插入字符数组;offset字符数组开始开始插入位置;len:插入长度。该方法有非常多的重载的方法,操作流程基本一致,无非是传入是数组的话就调用 arraycopy,是字符串则调用String 的 getChars 方法给数组赋值。

    public AbstractStringBuilder insert(int index, char[] str, int offset,
                                            int len)
        {
            //检查数组是否越界
            if ((index < 0) || (index > length()))
                throw new StringIndexOutOfBoundsException(index);
            if ((offset < 0) || (len < 0) || (offset > str.length - len))
                throw new StringIndexOutOfBoundsException(
                    "offset " + offset + ", len " + len + ", str.length "
                    + str.length);
            //数组扩容
            ensureCapacityInternal(count + len);
            //插入点后段后移
            System.arraycopy(value, index, value, index + len, count - index);
            //将插入数组str放入到value中
            System.arraycopy(str, offset, value, index, len);
            //更新长度
            count += len;
            return this;
        }

      假如value数组为:a,b,c,d,e ,插入数组为:str=f,g,h,index=1,offset=0,len=str.length=3,则count=5,大概执行流程如下:

      6、public String substring(int start, int end)

    public String substring(int start, int end) {
            if (start < 0)
                throw new StringIndexOutOfBoundsException(start);
            if (end > count)
                throw new StringIndexOutOfBoundsException(end);
            if (start > end)
                throw new StringIndexOutOfBoundsException(end - start);
            //调用String 构造方法截取
            return new String(value, start, end - start);
        }

      

      StringBuilder 和 StringBuffer 的区别:

      1.两者都继承了 AbstractStringBuilder 抽象类。

      2.前者不是线程安全的,后者是线程安全的,后者在每个方法都添加了 synchronized 关键字,所以它是线程安全的。

      3.两个都是维护一个可变的字符串;

      4.在单线程或对安全性不高的程序中,建议使用前者,在多线程中建议使用后者。

      总结:

      1、AbstractStringBuilder  和 String 的使用方式如出一辙;

      2、AbstractStringBuilder 的设计是为了解决String不可变的性质带来的问题的。

      3、AbstractStringBuilder 不能完全替代String,具体场景具体分析。

  • 相关阅读:
    Android自己定义无下划线ClickableSapn超链接文本样式
    poj 3263 Tallest Cow(线段树)
    html css 仿微信底部自己定义菜单
    oracle-闪回技术2
    poj 3181 Dollar Dayz
    poj 3181 Dollar Dayz
    poj 3046 Ant Counting(多重集组合数)
    poj 3046 Ant Counting(多重集组合数)
    【划分数】系列问题
    【划分数】系列问题
  • 原文地址:https://www.cnblogs.com/rainple/p/9874291.html
Copyright © 2020-2023  润新知