String StringBuffer StringBuilder AbstractStringBuilder的关联和源码解析

目录

前言

概括

StringBuffer类

StringBuilder类

AbstractStringBuilder

后话


前言

相信很多程序员在使用String、StringBuilder和StringBuffer的时候都知道怎么使用,却并不会去看其原理,这里我就整理一下自己的观看心得,如有不足,多多海涵哈!

在学习这三个类之前先认识一下CharSequence接口和Appendable接口:

CharSequence接口,出自于JDK1.4,有如下几个常用的方法:

int length();  返回字符序列长度

char charAt(int index);  返回字符序列在指定索引处的字符

CharSequence subSequence(int start, int end);  截取字符序列从索引start到end之间的值。包括start不包括end。例如:长度为5的字符序列“12345”,截图0到3的值为“123”,即真正的返回值为索引start到end-1之间的值。

Appendable接口,出自JDK1.5,有如下几个常用方法:

Appendable append(CharSequence csq) throws IOException;  拼接字符序列

Appendable append(CharSequence csq, int start, int end) throws IOException;  拼接字符序列指定区间内的字符序列,包括start不包括end。即真正拼接的值为索引start到end-1之间的值。

Appendable append(char c) throws IOException;  拼接字符

概括

String类是耳熟能详的类了,没什么可以介绍了的,只是在这里做一下简单的对比,String类适合很少量的字符串操作,做大量操作时相对其他StringBuilder和StringBuffer消耗时间和内存,而后面两者则都是适合做大量的拼接,截取,替换一类的操作,并且StringBuffer是线程安全的,StringBuilder是不安全的,值得一提的是这两者都继承于AbstractStringBuilder,两者本身并没有做太多的实际性工作,几乎所有的逻辑操作都在父类AbstractStringBuilder中,除了toString,toString是AbstractStringBuilder类的唯一的抽象方法。

StringBuffer类

StringBuffer类,继承AbstractStringBuilder,实现Serializable序列化,操作上是线程安全的,线程安全的原因就是该类进行数据操作的相关方法都加了synchronized关键字,而StringBuilder则都没有加。

toStringCache变量:使用transient修饰,不参与序列化,作为toString方法缓存用

默认构造器:调用父类构造器,传递一个默认的长度值16,父类构造器创建一个长度为16的字符数组;

参数为int的构造器参数为int的构造器意思是构造一个指定长度的字符数组

参数为String和CharSequence的构造器:会构造一个长度为(16+CharSequence字符长度)或者(16+String字符串长度)的字符数组。然后再拼接一下参数中的字符或者字符串,如果参数为null会抛出空指针异常(NullPointerException)。

length方法:返回实际数据长度

capacity方法:返回父类构造器的字符数组长度

 public final class StringBuffer extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence{

   
    private transient char[] toStringCache;

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    static final long serialVersionUID = 3388685877147921107L;

  
    public StringBuffer() {
        super(16);
    }

    public StringBuffer(int capacity) {
        super(capacity);
    }

   
    public StringBuffer(String str) {
        super(str.length() + 16);
        append(str);
    }

    
    public StringBuffer(CharSequence seq) {
        this(seq.length() + 16);
        append(seq);
    }

    @Override
    public synchronized int length() {
        return count;
    }

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

接下来看一看一些相关操作的方法:

可以看到如拼接,插入,删除,替换之类的操作都是调用父类的方法,并且返回的是调用该方法的类对象,这就表明StringBuffer类在进行数据改变的操作后返回类对象本身,原因就是父类中储存数据的字符数组value没有final所修饰,所以可以做到修改数据而不改变类对象。

而String类中储存数据的字符数组变量value是被final修饰的,也就说明无法对String类对象的值进行直接的修改,所以对其进行数据改变操作的返回值都是new String(XXX),也就是每个返回值都是一个新的String对象,所以String类不适合大量的数据值改变的操作。

toStringCache变量:只有在调用toString方法的时候给予赋值操作,临时储存数据,然后转换为Sting对象当做返回值。而每次对数据进行改变的时候都会重置变量值,保证每次toString之前该变量都是空。

    @Override
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }

    @Override
    public synchronized StringBuffer delete(int start, int end) {
        toStringCache = null;
        super.delete(start, end);
        return this;
    }


    @Override
    public synchronized StringBuffer replace(int start, int end, String str) {
        toStringCache = null;
        super.replace(start, end, str);
        return this;
    }


    @Override
    public synchronized StringBuffer insert(int index, char[] str, int offset,
                                            int len)
    {
        toStringCache = null;
        super.insert(index, str, offset, len);
        return this;
    }



    @Override
    public int indexOf(String str) {
        // Note, synchronization achieved via invocations of other StringBuffer methods
        return super.indexOf(str);
    }


    @Override
    public synchronized int indexOf(String str, int fromIndex) {
        return super.indexOf(str, fromIndex);
    }


    @Override
    public int lastIndexOf(String str) {
        // Note, synchronization achieved via invocations of other StringBuffer methods
        return lastIndexOf(str, count);
    }

    @Override
    public synchronized int lastIndexOf(String str, int fromIndex) {
        return super.lastIndexOf(str, fromIndex);
    }


    @Override
    public synchronized StringBuffer reverse() {
        toStringCache = null;
        super.reverse();
        return this;
    }

    @Override
    public synchronized String toString() {
        if (toStringCache == null) {
            toStringCache = Arrays.copyOfRange(value, 0, count);
        }
        return new String(toStringCache, true);
    }

StringBuilder类

StringBuilder类和StringBuffer差不多,少了一个toStringCache变量,所以的操作方法都没有添加sybchronized关键字,所以StringBuilder是线程不安全的类。就不多说了哈(*^▽^*)。

AbstractStringBuilder

AbstractStringBuilder产于JDK1.5,实现Appendable接口和CharSequence接口

该类只能被继承,有两个子类StringBuffer和StringBuilder,会默认调用有参构造器,指定初始化的字符串数据长度

value: 实例化时创建出来的字符数组

count: 实际数据包含的字符的长度

length方法:返回实际数据包含的字符的长度

capacity方法:返回字符数组的大小

代码如下:

abstract class AbstractStringBuilder implements Appendable, CharSequence {

    char[] value;

    int count;

    AbstractStringBuilder() {
    }

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

    
    @Override
    public int length() {
        return count;
    }

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

数据储存空间操作相关方法:

ensureCapacityInternal:每次进行数据改变操作之前都会调用的方法,其意在于确保数组变量value有能力承受接下来的改变,说白了就是对数据操作的之前改变字符数组大小,使其容量能足够接下来的操作使用而不出现错误。

MAX_ARRAY_SIZE:私有静态常量,值为 Integer.MAX_VALUE - 8,按照文档翻译来说,就是字符数组的最大容量,但是却没有达到Integer的最大值,原因是某些JVM虚拟机会在一个数组中保留一些标题词,如果强行尝试区分配超过这个容量的数组可能会导致抛出异常OutOfMemoryError:请求的数组大小超过VM限制。

newCapacity:重新设置字符数组的容量,并作为返回值。参数表示字符数组的最小容量,首先要知道字符串数组初始长度为16,扩容方式为原字符数组长度左移一位后再加2(原字符数组长度乘以2,再加2)。该方法就是比较一次扩容后value长度值newCapacity和参数指定的最小值minCapacity,如果一次扩容满足最小值需求,则使用newCapacity,如果不满于则直接使用minCapacity并且赋值于newCapacity,最后判断minCapacity的大小是否大于0并小于指定的MAX_ARRAY_SIZE的值,如果满足则返回minCapacity的值,如果不满足则表示要求的最小值超过了建议的最大值容量,那将把minCapacity传递给为hugeCapacity方法,并以该方法的返回值作为本方法的返回值,如下。

hugeCapacity:如果参数给定值超过Integer的类型最大值,抛出内存溢出异常,如果小于Integer的最大值,则和AbstractStringBuilder类的建议值对比,哪一个值大,则使用哪一个值作为返回值。

trimToSize:去除多余的数组储存空间,提高空间利用率。比较数组空间value的大小和实际数据count大小,如果实际数据元素小于value,则表示实际数据并未占满分配的空间,调用Arrays.copyOf方法把value中的空间铺满,返回铺满后的value值,

setLength:设置当前序列的长度为指定的参数长度newLength,如果当前序列的长度超出指定长度newLength,就把已有序列的前面长度为newLength的字符拷贝到新字符序列里,多出来的一部分舍弃。如果不超过newLegth,原有数据不变,就把缺少的几个位置的数据设置为空字符。

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


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

    private int hugeCapacity(int minCapacity) {
        if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
            throw new OutOfMemoryError();
        }
        return (minCapacity > MAX_ARRAY_SIZE)
            ? minCapacity : MAX_ARRAY_SIZE;
    }

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

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

接下来看一下实际的数据操作,这里以最常用的拼接和替换为例:

appendNull:先扩容,然后依次拼接字符n u l l,拼接的同时进行count++;

append:先判断字符串是否为null,如果是执行appendNull,如果不是空,则获取目标对象的长度,然后进行扩容,然后把目标对象拷贝到value的后面。然后更新数据的大小count。返回类对象

replace:先判断一系列索引越界问题,如果都没问题检查end是否超出count,超出了则修改end的值为count,获取目标字符串的长度,计算经过替换后的数据长度。原长度减去要被替换的长度加上要替换成的字符串长度:count-(end-start)+len。计算完成后更新字符数组大小,拷贝字符后更新count的值。返回类对象


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


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


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

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

后话

经过这么多的分析,大家有木有感觉突然明确清晰了很多呢,以后面试了也可以举一反三哦,哈哈,多看看源码其实并不难,相信你也可以的,感谢大家观看我的博客,如有不足欢迎指出,祝大家学习快乐,工作顺利哈

发布了6 篇原创文章 · 获赞 6 · 访问量 661

猜你喜欢

转载自blog.csdn.net/huangggf/article/details/103753327