Java字符串类之String、StringBuilder、StringBuffer源码分析与总结(你知道三者的区别?)

  前面分析完Java中常见容器的源码,此篇博客来分析下Java字符串相关的常用的三个类StringStringBuilderStringBuffer

  相信看过我前面的Java容器源码分析博客的小伙伴会发现一个规律,我一般分析某个类的源码都是从属性构造器(初始化方法)常用的API三个方向下手,并不会也没有必要从源码的第一行读到最后一行。在此篇博客分析这三个类也同样使用这种技巧,如果你需要了解更多的API实现可以自己去翻源码。

注明:以下源码分析都是基于jdk 1.8.0_221版本
在这里插入图片描述

String、StringBuilder、StringBuffer源码分析与总结目录

  • 一、`String`类
    • 1、`String`类概述
    • 2、`String`类主要属性
    • 3、`String`类主要构造器
    • 4、`String`类主要API
      • ①、`charAt`方法
      • ②、`equals`方法
      • ③、`startsWith`/`endsWith`方法
      • ④、`split`方法
  • 二、`AbstractStringBuilder`类
    • 1、`AbstractStringBuilder`类概述
    • 2、`AbstractStringBuilder`类主要属性
    • 3、`AbstractStringBuilder`类主要构造器
    • 4、`AbstractStringBuilder`类主要API
      • ①、`charAt`方法
      • ②、`setCharAt`方法
      • ③、`append`方法
      • ④、`deleteCharAt`方法
      • ⑤、`delete`方法
      • ⑥、`reverse`方法
  • 三、`StringBuilder`类
  • 四、`StringBuffer`类
  • 五、总结

一、String

1、String类概述

  String类的申明如下:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence

Java字符串类之String、StringBuilder、StringBuffer源码分析与总结(你知道三者的区别?)_第1张图片

  String类,相信只要用过Java的都知道这个类,但是有不少人对这个类有些误解String类是一个final类,并且实例中存放字符串内容的属性也是常量,无法修改字符串的内容,这貌似与我们的常识貌似有些不符。

  比如stringA = stringA + “123”,这不是能修改stringA吗?但是仔细观察这个计算式,其实你是将stringA对象指向的字符串内容与“123”拼接生成一个新的String对象,然后让stringA重新指向拼接好的对象。但是并没有修改stringA之前指向的字符串本身!通过这一个例子就想改变某些小伙伴的之前对String类的认识,貌似有些困难,你可以查一下它有那个API可以修改字符串的内容。

2、String类主要属性

  String类只有两个重要的属性:

/**
 * 存放字符串内容的char数组(注意这个属性是常量,也就说一旦初始化就无法修改)
 */
private final char value[];

/**
 * 字符串内容对应的hash值,调用hashCode方法才会设置,没有set相关的api
 */
private int hash; // Default to 0

3、String类主要构造器

  String类有十来个构造器,下面我只展示了比较常用的几个。

/**
 * 默认构造器,空串
 */
public String() {
    this.value = "".value;
}
/**
 * 从另一个String对象copy字符串内容
 */
public String(String original) {
    this.value = original.value;
    this.hash = original.hash;
}
/**
 * 从char数组copy
 */
public String(char value[]) {
    this.value = Arrays.copyOf(value, value.length);
}
/**
 * 指定插入数组copy的区间范围[offset, offset + count)
 */
public String(char value[], int offset, int count) {
	// 检查其实下标合法性
    if (offset < 0) {
        throw new StringIndexOutOfBoundsException(offset);
    }
    if (count <= 0) {
        if (count < 0) {
            throw new StringIndexOutOfBoundsException(count);
        }
        if (offset <= value.length) {
            this.value = "".value;
            return;
        }
    }
    // offset + count不能超过了value.length,会产生下标越界
    if (offset > value.length - count) {
        throw new StringIndexOutOfBoundsException(offset + count);
    }
    this.value = Arrays.copyOfRange(value, offset, offset+count);
}
/**
 * 从byte数组copy,指定区间范围[offset, offset + count),并且设置字符集(比如"UTF-8")
 */
public String(byte bytes[], int offset, int length, String charsetName) throws UnsupportedEncodingException {
    if (charsetName == null)
        throw new NullPointerException("charsetName");
    // checkBounds方法是检查offset、length的合法性,与上一个构造器检测是一样的
    checkBounds(bytes, offset, length);
    this.value = StringCoding.decode(charsetName, bytes, offset, length);
}
/**
 * 使用StringBuffer对象中的字符串内容初始化
 * (注意这里有个synchronized同步代码块,后面介绍StringBuffer类再回过头来看就知道了)
 */
public String(StringBuffer buffer) {
    synchronized(buffer) {
        this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
    }
}
/**
 * 使用StringBuilder对象的字符串内容初始化
 */
public String(StringBuilder builder) {
    this.value = Arrays.copyOf(builder.getValue(), builder.length());
}

4、String类主要API

①、charAt方法

/**
 * 通过下标获取字符串中的字符,String对象不支持 stringA[index]这种语法
 */
public char charAt(int index) {
    if ((index < 0) || (index >= value.length)) {
        throw new StringIndexOutOfBoundsException(index);
    }
    return value[index];
}

②、equals方法

/**
 * 比较两个字符串
 */
public boolean equals(Object anObject) {
    if (this == anObject) {
    	// 自己和自己比较肯定相等吧
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
        	// 当两个字符串的长度相同才有内容比较的意义
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}
/**
 * 忽略字母大小写比较字符串
 */
public boolean equalsIgnoreCase(String anotherString) {
	// 如果是自己和自己比较,直接返回true
	// 否则当anotherString != null,并且长度相等,才进行忽略字母大小写比较字符串
    return (this == anotherString) ? true : (anotherString != null)
            && (anotherString.value.length == value.length)
            && regionMatches(true, 0, anotherString, 0, value.length);
}

③、startsWith/endsWith方法

/**
 * 判断是否以prefix子串为前缀,比如“123456789”以“123”、“123456”等前缀开头
 */
public boolean startsWith(String prefix) {
	// 前缀判断从标0开始比较
    return startsWith(prefix, 0);
}
/**
 * 判断是否以suffix子串为后缀,比如“123456789”以“789”、“456789”等后缀结尾
 */
public boolean endsWith(String suffix) {
	// 后缀比较,从value.length - suffix.value.length开始比较
    return startsWith(suffix, value.length - suffix.value.length);
}
/**
 * 前缀、后缀的比较,通过调整toffset偏移下标
 */
public boolean startsWith(String prefix, int toffset) {
    char ta[] = value;
    int to = toffset;
    char pa[] = prefix.value;
    int po = 0;
    int pc = prefix.value.length;
    // Note: toffset might be near -1>>>1.
    if ((toffset < 0) || (toffset > value.length - pc)) {
        return false;
    }
    // 一个一个字符比较即可
    while (--pc >= 0) {
        if (ta[to++] != pa[po++]) {
            return false;
        }
    }
    return true;
}

④、split方法

/**
 * 字符串切割,比如“12a34a56”,使用子串"a"切割后,可得"12","34","45"三个字符串
 */
public String[] split(String regex) {
    return split(regex, 0);
}
/**
 * limit表示最多分成几段,limit == 0表示不限制
 */
public String[] split(String regex, int limit) {
    char ch = 0;
    // 这个if判断比较复杂
    if (
    	((regex.value.length == 1 && ".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) 
    	|| (regex.length() == 2 && regex.charAt(0) == '\\' && (((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 && ((ch-'a')|('z'-ch)) < 0 && ((ch-'A')|('Z'-ch)) < 0)) 
    	&& (ch < Character.MIN_HIGH_SURROGATE ||ch > Character.MAX_LOW_SURROGATE)
    ){
    	// 当regex的长度==1时,此时它不能是".$|()[{^?*+\\"字符串中的字符(正则表达式中的元字符)
    	// 当regex的长度==2时,第一个字符是反斜杠并且第二个不是ascii数字或ascii字母。
   		// 此时才能直接遍历求解
        int off = 0;
        int next = 0;
        boolean limited = limit > 0;
        ArrayList<String> list = new ArrayList<>();
        // 下面的操作比较简单,只要碰到一个regex就切开即可
        while ((next = indexOf(ch, off)) != -1) {
            if (!limited || list.size() < limit - 1) {
            	// 没有到达上限才可以继续切割剩余的部分
                list.add(substring(off, next));
                off = next + 1;
            } else {
                // 到达上限,最后一段当一整段,不再切割
                list.add(substring(off, value.length));
                off = value.length;
                break;
            }
        }
        // 没有切割出任何子串
        if (off == 0)
            return new String[]{this};
        if (!limited || list.size() < limit)
            list.add(substring(off, value.length));

        // Construct result
        int resultSize = list.size();
        if (limit == 0) {
            while (resultSize > 0 && list.get(resultSize - 1).length() == 0) {
                resultSize--;
            }
        }
        String[] result = new String[resultSize];
        return list.subList(0, resultSize).toArray(result);
    }
    // 否则它需要调用正则表达式进行处理
    return Pattern.compile(regex).split(this, limit);
}

  介绍此时,仍然没有出现修改String对象的方法,并不是我没有放上来,而是确实没有哇~

二、AbstractStringBuilder

  由于StringBuilderStringBuffer类都是AbstractStringBuilder的子类,所以我们先介绍下AbstractStringBuilder类,待会介绍StringBuilderStringBuffer类会轻松很多。

1、AbstractStringBuilder类概述

  AbstractStringBuilder类的申明如下:

abstract class AbstractStringBuilder implements Appendable, CharSequence

Java字符串类之String、StringBuilder、StringBuffer源码分析与总结(你知道三者的区别?)_第2张图片
  AbstractStringBuilder类是一个抽象类,实现了Appendable接口,并且字符串的内容可修改。可能有部分小伙伴觉得Java都已经设计了StringBuilderStringBuffer类,为啥还要设计它们的父类。主要是因为StringBuilderStringBuffer类功能基本是一样的,只不过后者支持多线程并发读写,提取出一个父类,可以减少代码的重复。

2、AbstractStringBuilder类主要属性

  AbstractStringBuilder类有两个属性:

/**
 * 存储字符串
 */
char[] value;

/**
 * 字符串长度
 */
int count;

3、AbstractStringBuilder类主要构造器

  AbstractStringBuilder类只有两个构造器,比较简单

/**
 * 默认构造器
 */
AbstractStringBuilder() {
}

/**
 * 指定数组初始化长度
 */
AbstractStringBuilder(int capacity) {
    value = new char[capacity];
}

4、AbstractStringBuilder类主要API

①、charAt方法

/**
 * 通过index访问字符串中的字符
 */
@Override
public char charAt(int index) {
    if ((index < 0) || (index >= count))
        throw new StringIndexOutOfBoundsException(index);
    return value[index];
}

②、setCharAt方法

/**
 * 修改字符串中指定下标的字符
 */
public void setCharAt(int index, char ch) {
    if ((index < 0) || (index >= count))
        throw new StringIndexOutOfBoundsException(index);
    value[index] = ch;
}

③、append方法

  append方法用于在尾端添加字符内容,此方法有很多个重载,我放了三个较为重要的,如有需要可以翻下源码。

/**
 * 将一个String对象的字符串内容拼接到当前AbstractStringBuilder的尾部
 */
public AbstractStringBuilder append(String str) {
    if (str == null)
        return appendNull();
    int len = str.length();
    // 确保value数组有足够的长度放下拼接的字符段
    ensureCapacityInternal(count + len);
    str.getChars(0, len, value, count);
    count += len;
    return this;
}
/**
 * 将一个AbstractStringBuilder对象(也可能是子类对象StringBuilder、StringBuffer)的字符串内容拼接到当前AbstractStringBuilder的尾部
 */
AbstractStringBuilder append(AbstractStringBuilder asb) {
    if (asb == null)
        return appendNull();
    int len = asb.length();
    // 确保value数组有足够的长度放下拼接的字符段
    ensureCapacityInternal(count + len);
    asb.getChars(0, len, value, count);
    count += len;
    return this;
}
/**
 * 将一个char数组的[offset, offset + len)的字符段拼接到当前对象尾部
 */
public AbstractStringBuilder append(char str[], int offset, int len) {
	// 确保value数组有足够的长度放下拼接的字符段
    if (len > 0)
        ensureCapacityInternal(count + len);
    System.arraycopy(str, offset, value, count, len);
    count += len;
    return this;
}

④、deleteCharAt方法

/**
 * 删除制定下标的字符
 */
public AbstractStringBuilder deleteCharAt(int index) {
    if ((index < 0) || (index >= count))
        throw new StringIndexOutOfBoundsException(index);
    // 由于存储字符的是数组,所以我们需要将[index + 1, count)的字符全部前移一个位置
    System.arraycopy(value, index+1, value, index, count-index-1);
    count--;
    return this;
}

⑤、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) {
    	// 需要将[end, count)段的字符全部前一个位置
        System.arraycopy(value, start+len, value, start, count-end);
        count -= len;
    }
    return this;
}

⑥、reverse方法

/**
 * 字符串内容翻转,比如"abcdefg"翻转后"gfedcba"
 */
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;
}

  AbstractStringBuilder类其实还有不少API,比如insertsubString以及对应的重载,受篇幅限制,就不一一放上来了,其实没有必要。

三、StringBuilder

  StringBuilder类的申明如下:

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

Java字符串类之String、StringBuilder、StringBuffer源码分析与总结(你知道三者的区别?)_第3张图片
  StringBuilder类的API都是对父类AbstractStringBuilderAPI的封装,只要看懂父类的相关API即可。下面这放了appenddelete方法,都是对父类API的封装,没啥好介绍的。。。
Java字符串类之String、StringBuilder、StringBuffer源码分析与总结(你知道三者的区别?)_第4张图片
Java字符串类之String、StringBuilder、StringBuffer源码分析与总结(你知道三者的区别?)_第5张图片

四、StringBuffer

  StringBuffer类的申明如下:

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

Java字符串类之String、StringBuilder、StringBuffer源码分析与总结(你知道三者的区别?)_第6张图片
  StringBuffer类的API也都是对父类AbstractStringBuilderAPI的封装,只不过加了synchronized关键字修饰,引入了锁机制。下面这放了appenddelete方法,都是对父类API的封装,没啥好介绍的。。。
Java字符串类之String、StringBuilder、StringBuffer源码分析与总结(你知道三者的区别?)_第7张图片
Java字符串类之String、StringBuilder、StringBuffer源码分析与总结(你知道三者的区别?)_第8张图片

五、总结

  总的来说,String对象是常量,无法修改字符串的内容,StringBuilderStringBuffer对象支持对字符串的修改(删除下标对应的字符、尾部添加字符串、插入字符串等),两者都是AbstractStringBuilder子类,不过StringBuilder不支持多线程并发读写,而StringBuffer通过synchronized关键字修饰方法(this对象锁),支持多线程并发读写,不过由于每次读写都要加锁、释放锁,所以StringBuffer的效率相对较低。

你可能感兴趣的:(Java,#,jdk源码分析)