jdk源码学习——AbstractStringBuilder类

16 篇文章 0 订阅
9 篇文章 0 订阅

类图结构

在这里插入图片描述

类的申明

1.默认访问控制修饰符,说明只能在包内使用,即只能在JDK内部使用,可能有人会问我创建一个java.lang包然后里面的类就可以使用AbstractStringBuilder类了,想法不错,但jkd不允许,会报SecurityException : Prohibited package name: java.lang。故这个类只是给StringBuffer和StringBuilder类使用的。
2.类名用abstract修饰说明是一个抽象类,只能被继承,不能直接创建对象。查了里面的方法你会发现它就一个抽象方法,toString方法。
3.实现了Appendable接口,Appendable能够被追加 char 序列和值的对象。如果某个类的实例打算接收来自 Formatter 的格式化输出,那么该类必须实现 Appendable 接口。
4.实现了Charsequence接口,代表该类,或其子类是一个字符序列。

abstract class AbstractStringBuilder implements Appendable, CharSequence {}

成员变量

    char[] value;
    int count;

value用于承装字符序列,count数组中实际存储字符的数量。这里的value同String类中的value不同,String类中的value是final的不可被修改,这里的value是动态的,并且可提供给外部直接操作。

构造函数

    AbstractStringBuilder() {
    }
    AbstractStringBuilder(int capacity) {
        value = new char[capacity];
    }
AbstractStringBuilder提供两个构造函数,一个是无参构造函数。一个是传一个capacity(代表数组容量)的构造,这个构造函数用于指定类中value数组的初始大小,数组大小后面还可动态改变。

其他重要方法

length

//返回已经存储字符序列的实际长度,即count的值。
    @Override
    public int length() {
        return count;
    }

capacity

//返回当前value可以存储的字符容量,即在下一次重新申请内存之前能存储字符序列的长度。
    // 新添加元素的时候,可能会对数组进行扩容。
    public int capacity() {
        return value.length;
    }

ensureCapacity

/**
     * 该方法是用来确保容量至少等于指定的最小值,是该类的核心也是其两个实现类StringBuffer和StringBuilder的核心。
     * 通过这种方式来实现数组的动态扩容。下面来看下其具体逻辑。
     * @param minimumCapacity
     */
    //1.判断入参minimumCapacity是否有效,即是否大于0,
    //大于0执行ensureCapacityInternal方法,小于等于0则忽略。
    public void ensureCapacity(int minimumCapacity) {
        if (minimumCapacity > 0)
            ensureCapacityInternal(minimumCapacity);
    }

    /**
     * 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}.
     */
    //2.判断入参容量值是否比原容量大,如果大于原容量,执行扩容操作,实际上就是创建一个新容量的数组,
    // 然后再将原数组中的内容拷贝到新数组中,如果小于或等于原容量则忽略。
    private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0) {
            value = Arrays.copyOf(value,
                    newCapacity(minimumCapacity));
        }
    }

    /**
     * The maximum size of array to allocate (unless necessary).
     * Some VMs reserve some header words in an array.
     * Attempts to allocate larger arrays may result in
     * OutOfMemoryError: Requested array size exceeds VM limit
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    /**
     * 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
     */
    /**
     * 3.计算新数组的容量大小,新容量取原容量的2倍加2和入参minCapacity中较大者。
     * 然后再进行一些范围校验。新容量必需在int所支持的范围内,之所以有<=0判断是因为,
     * 在执行 (value.length << 1) + 2操作后,可能会出现int溢出的情况。
     * 如果溢出或是大于所支持的最大容量(MAX_ARRAY_SIZE为int所支持的最大值减8),
     * 则进行hugeCapacity计算,否则取newCapacity
     * @param minCapacity
     * @return
     */
    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;
    }

    /**
     * 4.这一步先进行范围检查,必须在int所支持的最大范围内。
     * 然后在minCapacity与MAX_ARRAY_SIZE之间取较大者,
     * 此方法取的范围是Integer.MAX_VALUE - 8到Integer.MAX_VALUE之间的范围。
     * @param minCapacity
     * @return
     */
    private int hugeCapacity(int minCapacity) {
        if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
            throw new OutOfMemoryError();
        }
        return (minCapacity > MAX_ARRAY_SIZE)
            ? minCapacity : MAX_ARRAY_SIZE;
    }

    /**
     * 5.总结:
     * 1.通过value = Arrays.copyOf(value,newCapacity(minimumCapacity));进行扩容
     * 2.新容量取 minCapacity,原容量乘以2再加上2中较大的,但不能大于int所支持的最大范围。
     * 3.在实际环境中在容量远没达到MAX_ARRAY_SIZE的时候就报OutOfMemoryError异常了,其实就是在复制的时候创建了数组char[] copy = new char[newLength];这里支持不了那么大的内存消耗,可以通过 -Xms256M -Xmx768M设置最大内存。
     */

trimToSize

 /**
     * 减少字符序列的使用空间,比如申请了100字符长度的空间,但是现在只用了60个,
     * 那剩下的40个无用的空间放在那里占内存,可以调用此方法释放掉未用到的内存。
     * 原理很简单,只申请一个count大小的数组把原数组中的内容复制到新数组中,
     * 原来的数组由于没有被任何引用所指向,之后会被gc回收。
     */
    public void trimToSize() {
        if (count < value.length) {
            value = Arrays.copyOf(value, count);
        }
    }

setLength

/**
     * 用空字符填充未使用的空间。首先对数组进行扩容,
     * 然后将剩余未使用的空间全部填充为'0'字符。
     * @param newLength
     */
    public void setLength(int newLength) {
        if (newLength < 0)
            throw new StringIndexOutOfBoundsException(newLength);
        ensureCapacityInternal(newLength);

        if (count < newLength) {
            Arrays.fill(value, count, newLength, '\0');
        }

        count = newLength;
    }

charAt

/**
     * 获取字符序列中指定位置的字符,范围为0到count,
     * 超出范围抛StringIndexOutOfBoundsException异常。
     * @param index
     * @return
     */
    @Override
    public char charAt(int index) {
        if ((index < 0) || (index >= count))
            throw new StringIndexOutOfBoundsException(index);
        return value[index];
    }

codePointAt

/**
     * 获取字符序列中指定位置的字符,所对应的代码点,即ascii码。
     * @param index
     * @return
     */
    public int codePointAt(int index) {
        if ((index < 0) || (index >= count)) {
            throw new StringIndexOutOfBoundsException(index);
        }
        return Character.codePointAtImpl(value, index, count);
    }

codePointBefore

/**
     * 获取字符序列中指定位置的前一个位置的字符,所对应的代码点。
     * @param index
     * @return
     */
    public int codePointBefore(int index) {
        int i = index - 1;
        if ((i < 0) || (i >= count)) {
            throw new StringIndexOutOfBoundsException(index);
        }
        return Character.codePointBeforeImpl(value, index, 0);
    }

getChars

/**
     * 将字符序列中指定区间srcBegin到srcEnd内的字符拷贝到dst字符数组中从dstBegin开始往后的位置中。
     * @param srcBegin
     * @param srcEnd
     * @param dst
     * @param 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");
        System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
    }

setCharAt

/**
     * 设置字符序列中指定索引index位置的字符为ch。
     * @param index
     * @param ch
     */
    public void setCharAt(int index, char ch) {
        if ((index < 0) || (index >= count))
            throw new StringIndexOutOfBoundsException(index);
        value[index] = ch;
    }

append系列

以append(String str)为例

 /**
     * 1.首先判断所传参数是否为null,如果为null则调用appendNull方法,实际上就是在原字符序列后加上"null"序列。
     * 2.如果不为null则进行扩容操作,最小值为count+len,这一步可能增加容量也可能不增加,当count+len小于或等于capacity就不用进行扩容。
     * 3.然后再将参数的字符串序列添加到value中。
     * 4.最后返回this,注意这里返回的是this,也就意味者,可以在一条语句中多次调用append方法,即大家所知的方法调用链。原理简单,但思想值得借鉴。asb.append("hello").append("world");
     * @param str
     * @return
     */
    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;
    }

delete

/**
     * 删除字符序列指定区间的内容。这个操作不改变原序列的容量。
     * @param start
     * @param end
     * @return
     */
    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) {
            System.arraycopy(value, start+len, value, start, count-end);
            count -= len;
        }
        return this;
    }

deleteCharAt

/**
     * 删除字符序列中指定索引index位置的字符
     * @param index
     * @return
     */
    public AbstractStringBuilder deleteCharAt(int index) {
        if ((index < 0) || (index >= count))
            throw new StringIndexOutOfBoundsException(index);
        System.arraycopy(value, index+1, value, index, count-index-1);
        count--;
        return this;
    }

replace

/**
     * 将原字符序列指定区间start到end区间内的内容替换为str,
     * 替换过程中序列长度会改变,所以需要进行扩容和改就count的操作。
     * @param start
     * @param end
     * @param str
     * @return
     */
    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);
        ensureCapacityInternal(newCount);

        System.arraycopy(value, end, value, start + len, count - end);
        str.getChars(value, start);
        count = newCount;
        return this;
    }

substring

/**
     * 切割原字符序列指定区间start到end内的内容,返回字符串形式。
     * @param start
     * @return
     */
    public String substring(int start) {
        return substring(start, count);
    }
    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);
        return new String(value, start, end - start);
    }

indexOf

/**
     * 查询给定字符串在原字符序列中第一次出现的位置。调用的其实是String类的indexOf方法
     * @param str
     * @return
     */
    public int indexOf(String str) {
        return indexOf(str, 0);
    }

reverse

/**
     * 该方法用于将字符序列反转
     * 1.hasSurrogates用于判断字符序列中是否包含surrogates pair
     * 2.将字符反转,count为数组长度,因为是从0开始的所以这里需要减1。具体转换是第一个字符与最后一个字符对调,第二个字符与倒数第二个字符对调,依次类推
     * 3.实际上上述操作只需要循环(n-1) /2 + 1次[判断条件j>=0所以要+1次,源码中>>1就是除以2]就可以了,如数组长度为9则需要循环 (9-1-1)/2 +1 = 4次,9个字符对调次,第5个位置的字符不用换,如果长度为10需要循环(10-1-1)/2 +1 = 5次
     * 4.剩下的工作就是两个位置的元素互换。
     * 5.如果序列中包含surrogates pair 则执行reverseAllValidSurrogatePairs方法
     * @return
     */
    public AbstractStringBuilder reverse() {
        boolean hasSurrogates = false;
        int n = count - 1;
        for (int j = (n-1) >> 1; j >= 0; j--) {
            int k = n - j;
            char cj = value[j];
            char ck = value[k];
            value[j] = ck;
            value[k] = cj;
            if (Character.isSurrogate(cj) ||
                Character.isSurrogate(ck)) {
                hasSurrogates = true;
            }
        }
        if (hasSurrogates) {
            reverseAllValidSurrogatePairs();
        }
        return this;
    }

    /**
     * Surrogate Pair是UTF-16中用于扩展字符而使用的编码方式,是一种采用四个字节(两个UTF-16编码)来表示一个字符。
     * char在java中是16位的,刚好是一个UTF-16编码。而字符串中可能含有Surrogate Pair,但他们是一个单一完整的字符,只不过是用两个char来表示而已,因此在反转字符串的过程中Surrogate Pairs 是不应该被反转的。而reverseAllValidSurrogatePairs方法就是对Surrogate Pair进行处理。
     */
    /** Outlined helper method for reverse() */
    private void reverseAllValidSurrogatePairs() {
        for (int i = 0; i < count - 1; i++) {
            char c2 = value[i];
            if (Character.isLowSurrogate(c2)) {
                char c1 = value[i + 1];
                if (Character.isHighSurrogate(c1)) {
                    value[i++] = c1;
                    value[i] = c2;
                }
            }
        }
    }

toString

/**
     * 需要子类自己去实现
     * @return
     */
    @Override
    public abstract String toString();
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值