JDK源码 -- 字符串String/StringBuffer/StringBuilder

一:String

String是工作学习中日常处理使用的类,在本文的第一大节点根据String继承结构、构造函数、主要属性/方法、字符串比较等几方面揭开这个类神秘的面纱

1.1 继承结构
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence

上述代码可以一目了然的看到String类实现了SerializableComparable接口,可能不太熟悉的CharSequence接口下面也会一一娓娓道来

1.1.1 Serializable

首先Serializable接口就不用太详细阐述,标识性接口,序列化操作必须实现的接口。在这想到做一个Doubbo项目时需要完成Excel导出功能,当时使用ModelAndView类一直报错提示不能序列化,查看源码发现该类未实现Serializable接口

1.1.2 Comparable

针对Comparable接口想必最早的学习接触应该在集合模块,Java中两个比较器,Comparable接口提供comparaTo()方法,ComparaTor类提供compara()方法。详细细节留待后面介绍,String在这里实现Comparable接口并重写comparaTo()方法,那么TreeSet、TreeMap中String元素就拥有字典排序顺序

1.1.3 CharSequence
Appendable append(CharSequence csq) throws IOException;

CharSequence接口下面的实现类有StringBuffer、StringBuilder等,提供方法不多,主要作用在Appendable接口中的append()系列重载方法可以体现。StringBuffer、StringBuilder都对Appendable接口进行了相应实现,在里面append()参数为CharSequence类型

1.2 主要属性
private final char value[];

private int hash; // Default to 0

String类属性不多,集中在char[]类型的value属性和hash属性上。最主要的value属性才是String核心,该属性保存String内容。注意一点该属性为char类型数组,数组是不可更改的数据结构,这也是String创建后的任何操作修改都是产生新对象的原因。在一些大字符串对象修改频繁场合慎重使用String,对象创建销毁将是一笔不可忽略的开销

1.3 构造函数

String提供六种对应构造,参数分别为空、String、char[]、int[]、byte[]、StringBuilder/StringBuffer。其中参数为空的构造函数没有任何实际使用意义,String使用定长数组实现,任何修改都会产生新对象。使用String、StringBuffer/StringBuilder构造为同一原理,只需将对应的value属性取出并进行赋值即可

char[]、int[]数组又类似,value属性类型为char[]自不必多说,int[]类型参数多了一步将int数据转为对应char类型数据再构造新数组

1.4 主要方法

对于String方法描述一点不想重复阐述API,个人理解就分为两类,查询value属性与修改value属性值。如下代码所示:

//都是对value[]的长度进行查询得出结论
public int length() {
    return value.length;
}

public boolean isEmpty() {
    return value.length == 0;
}

//操作String的value属性进行数组内容比较
public int compareTo(String anotherString) {
    int len1 = value.length; //取出当前对象value长度
    int len2 = anotherString.value.length;  //取出比较String的value长度
    int lim = Math.min(len1, len2);
    char v1[] = value;
    char v2[] = anotherString.value;

    int k = 0;
    while (k < lim) { //循环操作最小长度次数
        char c1 = v1[k];
        char c2 = v2[k];
        if (c1 != c2) {
            return c1 - c2; //返回不相等位置字典顺序
        }
        k++;
    }
    return len1 - len2;  
}
public String substring(int beginIndex, int endIndex) {
    if (beginIndex < 0) {
        throw new StringIndexOutOfBoundsException(beginIndex);
    }
    if (endIndex > value.length) {
        throw new StringIndexOutOfBoundsException(endIndex);
    }
    int subLen = endIndex - beginIndex;
    if (subLen < 0) {
        throw new StringIndexOutOfBoundsException(subLen);
    }
    return ((beginIndex == 0) && (endIndex == value.length)) ? this
            : new String(value, beginIndex, subLen); // 经过判断比较后截取对应长度value重新赋值构成新String对象
}
1.6 字符串比较

搞清楚一下几个关键点String任何形式比较轻松拿下:

  • String创建有字面量即常量与创建String对象两种方式
  • 字面量String位于常量池中,相同字面量内存地址引用相同
  • String对象内存地址位于堆中,相同内容两个对象地址不一致
  • intern()方法在jdk1.7开始判断堆地址存在该对象后,将地址引用到常量池保存一份,故后面所有创建相同内容的String字面量都为同一内存地址
String a1 = "a";
String a2 = "a";
String a3 = new String("a");
String a4 = new String("a");
String a5 = new String("a") + new String("b");
a5.intern();
String a6 = "ab";
System.out.println(a1 == a2); //true
System.out.println(a2 == a3); //false
System.out.println(a3 == a4); //false
System.out.println(a5 == a6); //true

二:StringBuffer/StringBuilder

StringBuffer与StringBuilder的结构实现与String类似,String是定长数组,每次更改都是新对象返回。StringBuilder/StringBuffe则是变长数组,那么这个变长如何实现则是接下来讨论的重点

2.1 继承结构

JDK源码 -- 字符串String/StringBuffer/StringBuilder

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

value属性默认值大小为16,如果是根据String等进行初始化则是参数长度+16

2.2 变长数组实现
public AbstractStringBuilder append(String str) {
    if (str == null)
        return appendNull();
    int len = str.length();
    ensureCapacityInternal(count + len); // count表示现在字符串长度
    str.getChars(0, len, value, count);
    count += len;
    return this;
}

append()方法是常见StringBuffer/StringBuilder用来修改自身内容方法,找到对应AbstractStringBuilder类中的该方法,这里首先是判空后求出增加对象字符串长度,将其作为参数与原对象长度求和传至ensureCapacityInternal(count + len)

private void ensureCapacityInternal(int minimumCapacity) {
    // overflow-conscious code
    if (minimumCapacity - value.length > 0)
        expandCapacity(minimumCapacity);
}

ensureCapacityInternal()方法直接比较字符串长度之和与现有value属性数组长度大小,如果大于现有value数组长度则进入expandCapacity(minimumCapacity)方法

void expandCapacity(int minimumCapacity) {
    int newCapacity = value.length * 2 + 2; //对现有数组进行扩容
    if (newCapacity - minimumCapacity < 0) //判断扩容后容量是否满足
        newCapacity = minimumCapacity;  //如果过小则采用新字符串所需长度
    if (newCapacity < 0) { 
        if (minimumCapacity < 0) // overflow
            throw new OutOfMemoryError();
        newCapacity = Integer.MAX_VALUE;
    }
    value = Arrays.copyOf(value, newCapacity); //对原数组进行赋值并扩容的操作
}

在这个方法中才是真正的对value数组进行扩容,扩容方式简单粗暴,就是产生最新的数组赋值给原有的value属性即可

2.3 线程安全

StringBuffer相对于StringBuilder是线程安全的,这点的实现仅仅是在方法上加synchronized关键字而已,当然线程安全也就意味着效率上StringBuilder优于StringBuffer