Java8源码 java.lang 包 03、AbstractStringBuilder 类

概述

String 类中有不少地方使用了 StringBuffer 和 StringBuilder 类,而这两个类都是继承自 AbstractStringBuilder 类,里面的很多实现都是直接使用父类的。

AbstractStringBuilder类的子类

类的定义

abstract class AbstractStringBuilder implements Appendable, CharSequence 
  1. 默认访问控制修饰符,说明只能在包内(jdk内)使用。即这个类只是给 StringBuffer 和 StringBuilder 类使用
  2. 是一个抽象类,只能被继承,不能直接创建对象。只有一个 toString 的抽象方法
  3. 实现了 Appendable 接口,Appendable 能够被追加 char 序列和值的对象。如果某个类的实例打算接收来自 Formatter 的格式化输出,那么该类必须实现 Appendable 接口,重写 append 方法
  4. 实现了 Charsequence 接口,代表该类,或其子类是一个字符序列

成员变量

    //用于存储字符序列
    char[] value;
    //数组中实际存储字符的数量
    int count;

这里的 value 和 String 类中的不同,是动态的,可提供给外部直接操作。

构造方法

    AbstractStringBuilder() {
    }

    AbstractStringBuilder(int capacity) {
        value = new char[capacity];
    }

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

常用方法

实现CharSequence的方法

length()

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

charAt(int)

    @Override
    public char charAt(int index) {
        if ((index < 0) || (index >= count))
            throw new StringIndexOutOfBoundsException(index);
        return value[index];
    }

subSequence

    @Override
    public CharSequence subSequence(int start, int end) {
        return substring(start, 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);
        return new String(value, start, end - start);
    }

getValue

    final char[] getValue() {
        return value;
    }

capacity

    public int capacity() {
        return value.length;
    }

返回当前 value 可以存储的字符容量,即在下一次重新申请内存之前能存储字符序列的长度。新添加元素的时候,可能会对数组进行扩容。

ensureCapacity

该方法是用来确保容量至少等于指定的最小值,是该类的核心也是其两个实现类 StringBuffer 和 StringBuilder 的核心。通过这种方式来实现数组的动态扩容。下面来看下其具体逻辑。

逻辑

1.判断入参 minimumCapacity 是否有效,即是否大于 0,大于 0 执行 ensureCapacityInternal() 方法,小于等于0 则忽略。

    public void ensureCapacity(int minimumCapacity) {
        if (minimumCapacity > 0)
            ensureCapacityInternal(minimumCapacity);
    }   

2.判断入参容量值是否比原容量大,如果小于或等于原容量则忽略。如果大于原容量,执行扩容操作。
实际上就是创建一个新容量的数组,然后再将原数组中的内容拷贝到新数组中。

    private void ensureCapacityInternal(int minimumCapacity) {
        if (minimumCapacity - value.length > 0) {
            value = Arrays.copyOf(value, newCapacity(minimumCapacity));
        }
    }

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

    // 要分配的数组的最大大小
	private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;    
    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 之间的范围。

	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. 通过 value = Arrays.copyOf(value,newCapacity(minimumCapacity)); 进行扩容
  2. 新容量取 minCapacity和原容量乘以2再加上2中较大的那个,但不能大于 Integer.MAX_VALUE - 8 。
  3. 在实际环境中在容量远没达到 MAX_ARRAY_SIZE 的时候就报 OutOfMemoryError 异常了,其实就是在复制的时候创建了数组 char[] copy = new char[newLength]; 这里支持不了那么大的内存消耗,可以通过
    -Xms256M -Xmx768M 设置最大内存。

trimToSize

减少字符序列的使用空间,比如申请了100字符长度的空间,但是现在只用了 60 个,那剩下的 40 个无用的空间放在那里占内存,可以调用此方法释放掉未用到的内存。

    public void trimToSize() {
        if (count < value.length) {
            value = Arrays.copyOf(value, count);
        }
    }

原理很简单,只申请一个 count 大小的数组把原数组中的内容复制到新数组中,原来的数组由于没有被任何引用所指向,之后会被 gc 回收。

setLength

用空字符填充未使用的空间。首先对数组进行扩容,然后将剩余未使用的空间全部填充为’0’字符。

    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;
    }
	
	// Arrays类中的fill方法
    public static void fill(char[] a, int fromIndex, int toIndex, char val) {
        rangeCheck(a.length, fromIndex, toIndex);
        for (int i = fromIndex; i < toIndex; i++)
            a[i] = val;
    }

getChars

将字符序列中指定区间 srcBegin 到 srcEnd 内的字符拷贝到 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");
        System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
    }

append 系列

AbstractStringBuilder 类中有一系列 append 方法,作用是在原字符序列后添加给定的对象或元素所对应的字符序列。这里挑一个代表讲解,其它方法原理类似。

    public AbstractStringBuilder append(Object obj) {
        return append(String.valueOf(obj));
    }

	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;
    }
  1. 首先判断所传参数是否为 null,如果为 null 则调用 appendNull 方法,它会在原字符序列后加上 “null” 序列(如果有必要的话会先进行扩容)。
  2. 如果不为 null 则进行扩容操作,最小值为 count+len,这一步可能增加容量也可能不增加。
  3. 将参数的字符串序列添加到 value 中。
  4. 最后返回 this,注意这里返回的是 this,也就意味者,可以在一条语句中多次调用 append 方法,即调用链。原理简单,但思想值得借鉴。asb.append(“hello”).append(“world”);

delete

删除字符序列指定区间的内容。如果 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) {
            System.arraycopy(value, start+len, value, start, count-end);
            count -= len;
        }
        return this;
    }

deleteCharAt

删除字符序列中指定索引 index 位置的字符。

    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。

    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;
    }

替换过程中序列长度会改变,所以需要进行扩容和改就 count 的操作。

substring

切割原字符序列指定区间 start 到 end 内的内容,返回结果子串。

    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);
    }

insert 系列

insert 系列作用是将给定对象所对应的字符串插入到原序列的指定位置。insert 系列同 append 系列类似,只不过 append 是在原序列末尾添加元素,insert 是在指定位置插入元素。这里选一个代表进行讲解。

    public AbstractStringBuilder insert(int offset, String str) {
        if ((offset < 0) || (offset > length()))
            throw new StringIndexOutOfBoundsException(offset);
        if (str == null)
            str = "null";
        int len = str.length();
        ensureCapacityInternal(count + len);
        System.arraycopy(value, offset, value, offset + len, count - offset);
        str.getChars(value, offset);
        count += len;
        return this;
    }

indexOf

查询给定字符串在原字符序列中第一次出现的位置。

    public int indexOf(String str) {
        return indexOf(str, 0);
    }
    public int indexOf(String str, int fromIndex) {
        return String.indexOf(value, 0, count, str, fromIndex);
    }

lastIndexOf

返回指定字符在此字符串中最后一次出现处的索引。

    public int lastIndexOf(String str) {
        return lastIndexOf(str, count);
    }
    public int lastIndexOf(String str, int fromIndex) {
        return String.lastIndexOf(value, 0, count, str, fromIndex);
    }

reverse

将字符序列反转。如 “hellow” 执行 reverse 后变成 “wolleh”。

    public AbstractStringBuilder reverse() {
        // 判断字符序列中是否包含surrogates pair
        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;
    }

 	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;
                }
            }
        }
    }
  1. 将字符反转,count 为数组长度,因为是从 0 开始的所以这里需要减 1。具体转换是第一个字符与最后一个字符对调,第二个字符与倒数第二个字符对调,依次类推
  2. 实际上上述操作只需要循环 (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 次
  3. 剩下的工作就是两个位置的元素互换
  4. 如果序列中包含 surrogates pair 则执行 reverseAllValidSurrogatePairs 方法

Surrogate Pair 是 UTF-16 中用于扩展字符而使用的编码方式,采用四个字节(两个 UTF-16 编码)来表示一个字符。 char 在 java 中是 16 位的,刚好是一个 UTF-16 编码。而字符串中可能含有 Surrogate Pair ,但他们是一个单一完整的字符,只不过是用两个 char 来表示而已,因此在反转字符串的过程中 Surrogate Pairs 是不应该被反转的。而 reverseAllValidSurrogatePairs 方法就是对 Surrogate Pair 进行处理。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值