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

版权声明:转载原创博客请附上原文链接 https://blog.csdn.net/weixin_43495590/article/details/88708663

一:String

涉及String提到的应该就是太监类定长数组实现字面量与对象比较这三方面,接下来就是从继承结构、主要属性、方法、构造函数解析这个类

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

实现SerializableComparable接口,可序列化标志性接口与比较器接口,最后一个比较陌生的CharSequence接口个人理解为字符串描述接口,查看String、StringBuilder、StringBuffer源码发现都实现了该接口。针对实现CharSequence接口最大的作用莫过于在StringBuilder类的append()方法参数为CharSequence,这就不难理解为什么会把CharSequence接口描述为字符串描述接口

1.2 主要属性
	private final char value[];
    public String substring(int beginIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        int subLen = value.length - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        // 最后在返回String的时候是new创建
        return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
    }

注意保存数据的value属性为char类型数组数组是不可更改的数据结构,所以String创建后的任何修改操作都会产生新对象,例如subString()。少数字符串拼接修改操作可以使用String,频繁亦或是大规模改动使用StringBuilder

1.3 构造函数

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

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

1.4 主要方法

对于String方法描述一点不想重复阐述API,个人理解就分为两类,查询value属性与修改value属性值,总结大部分方法实现都是对数组操作实现

1.6 字符串比较
  • 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
String s = null;
String ss = "";

除了上述使用==判定字符串相等之外,还需要注意字符串的null"",字符串的判定一般都会这两方面同时判断,因为前者代表空对象会引发空指针异常后者则代表空字符串

二:StringBuffer/StringBuilder

StringBuilder/StringBuffe是变长数组,变长即动态扩容是关注重点。当然还有一点就是线程安全问题

2.1 继承结构
 public final class StringBuffer
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
 abstract class AbstractStringBuilder implements Appendable, CharSequence

StringBuilder/StringBuilder都继承抽象类AbstractStringBuilder,该抽象类继承结构上的作用就是实现接口Appendable,Appendable接口定义append()方法系列重载。同时类中定义储存元素的char类型value[]数组以及记录数组使用量count元素

2.2 构造方法
public StringBuffer() {
    super(16);
}
public StringBuffer(String str) {
    super(str.length() + 16);
    append(str);
}
......

StringBuilder/StringBuffer实例化默认创建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()方法为例,找到对应AbstractStringBuilder类中的该方法,这里首先是判空后求出增加对象字符串长度(CharSequence定义length属性记录字符串长度),将其作为参数与原字符串长度求和传至ensureCapacityInternal()

扫描二维码关注公众号,回复: 6028305 查看本文章
private void ensureCapacityInternal(int minimumCapacity) {
    // overflow-conscious code
    if (minimumCapacity - value.length > 0)
        expandCapacity(minimumCapacity);
}

ensureCapacityInternal()方法直接比较字符串长度之和与现有数组长度大小,如果大于现有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); //对原数组进行赋值并扩容的操作
}

2.3 线程安全

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

三:StringJoiner

JDK1.8提供具有强大API的字符串操作类,底层采用StringBuilder实现,线程非安全。在前缀后缀分隔方面表现优异

3.1 构造函数
    public StringJoiner(CharSequence delimiter) {
        this(delimiter, "", "");
    }
    public StringJoiner(CharSequence delimiter,
                        CharSequence prefix,
                        CharSequence suffix) {
        Objects.requireNonNull(prefix, "The prefix must not be null");
        Objects.requireNonNull(delimiter, "The delimiter must not be null");
        Objects.requireNonNull(suffix, "The suffix must not be null");
        // make defensive copies of arguments
        this.prefix = prefix.toString();
        this.delimiter = delimiter.toString();
        this.suffix = suffix.toString();
        this.emptyValue = this.prefix + this.suffix;
    }    

前缀prefix、后缀suffix、分隔符delimiter

3.2 主要方法
	// 底层实现所需的StringBuilder实例
  	private StringBuilder value;
	
	//在进行添加操作的时候就是调用StringBuilder的append()方法
	//方法prepareBuilder就是进行StringBuilder实例化操作
    public StringJoiner add(CharSequence newElement) {
        prepareBuilder().append(newElement);
        return this;
    }
    
    //私有方法实例化StringBuilder
    private StringBuilder prepareBuilder() {
        if (value != null) {
            value.append(delimiter); //已进行实例化,需加分隔符
        } else {
        	//未实例化,需要加上前缀
            value = new StringBuilder().append(prefix);
        }
        return value;
    }

StringJoiner类的append()方法通过调用StringBuilder的append()方法实现,相较于StringBuilder的区别就在于在进行StringBuilder实例化的时候对prefix、suffix、delimiter进行了处理

3.3实践举例
    StringJoiner sj1 = new StringJoiner(",");
    sj1.add("1");
    sj1.add("2");
    StringJoiner sj2 = new StringJoiner(",","prefir","suffix");
    sj1.add("add");
    System.out.println(sj1); //输出结果为 1,2
    System.out.println(sj2); //输出结果为 prefix,add,suffix

猜你喜欢

转载自blog.csdn.net/weixin_43495590/article/details/88708663