本文剖析了 String 的源码(1.8),主要讲述了 String 的一些特性以及一些核心方法的设计与实现。


一、String 的不可变性

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];
}

上面的String 源码解释了 String 的不可变性。

  1. String 被 final 修饰,意味着该类不能被继承,也就没有子类,无法通过继承的方式覆写 String 的方法。

    final 修饰 class,可以参考 Java 虚拟机规范

    A class can be declared final if its definition is complete and no subclasses are desired or required.

    It is a compile-time error if the name of a final class appears in the extends clause (§8.1.4) of another class declaration; this implies that a final class cannot have any subclasses.

    It is a compile-time error if a class is declared both final and abstract, because the implementation of such a class could never be completed (§8.1.1.1).

    Because a final class never has any subclasses, the methods of a final class are never overridden (§8.4.8.1).

  2. String 底层使用一个 value 字符数组来存储,使用 privatefinal 修饰,意味着 value 一旦被赋值,内存地址不会再改变(final),并且不能再被修改(private

二、String.intern() 方法

String 类的intern()方法涉及到一个叫做 “常量池“ 的概念,可以理解成 Java 系统级别的缓存。

  • 直接使用双引号声明出来的String对象会直接存储在常量池中。
  • 如果不是用双引号声明的String对象,可以使用String提供的intern方法。intern 方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中

JAVA 使用 jni 调用c++实现的StringTableintern方法, StringTableintern方法跟Java中的HashMap的实现是差不多的, 只是不能自动扩容。默认大小是1009。

要注意的是,String的String Pool是一个固定大小的Hashtable,默认值大小长度是1009,如果放进String Pool的String非常多,就会造成Hash冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是当调用String.intern时性能会大幅下降(因为要一个一个找)。

在 jdk6中StringTable是固定的,就是1009的长度,所以如果常量池中的字符串过多就会导致效率下降很快。在jdk7中,StringTable的长度可以通过一个参数指定:

  • -XX:StringTableSize=99991

三、String 的构造函数和若干重要方法

1. 构造函数

String 类有4个核心构造函数

// String 为参数的构造方法
public String(String original) {
    this.value = original.value;
    this.hash = original.hash;
}
// char[] 为参数构造方法
public String(char value[]) {
    this.value = Arrays.copyOf(value, value.length);
}
// 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());
}

2. 使用频率很高的几个方法

length()

// 字符串长度
public int length() {
    return value.length;
}

isEmpty()

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

charAt()

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

equals()equalsIgnoreCase()

public boolean equals(Object anObject) {
    // 对象引用相同直接返回 true
    if (this == anObject) {
        return true;
    }
    // 判断需要对比的值是否为 String 类型,如果不是则直接返回 false
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        // 比较底层数组长度,长度不同,直接返回 false,长度相同,遍历字符数组,循环比较,只要有一个字符不同,返回 false
        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;
}

直接使用 equals() 方法时,应该小心空指针。非空字符串放在前面,或者直接使用 apache commons 相关工具类 StringUtils

// StringUtils::equals
public static boolean equals(String str1, String str2) {
        return str1 == null ? str2 == null : str1.equals(str2);
}

equalsIgnoreCase(): 该方法 与 equals() 不同之处在于前者忽略大小写比较字符串。

compareTo()compareToIgnoreCase()

// 返回值为 0 表示等于,正数表示大于,负数表示小于
public int compareTo(String anotherString) {
    int len1 = value.length;
    int len2 = anotherString.value.length;
    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;
}

compareToIgnoreCase():比较忽略大小写

其他

  • indexOf():查询字符串首次出现的下标位置
  • lastIndexOf():查询字符串最后出现的下标位置
  • contains():查询字符串中是否包含另一个字符串
  • toLowerCase():把字符串全部转换成小写
  • toUpperCase():把字符串全部转换成大写
  • trim():去掉字符串首尾空格
  • replace():替换字符串中的某些字符
  • split():把字符串分割并返回字符串数组
  • join():把字符串数组转为字符串

参考: