String 源码阅读

237 阅读7分钟

本文基于 JDK1.8,在其他版本中会有不同。

定义及属性

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

String 类被 final 修饰,表示不可变。实现了 Serializable, Comparable<String>, CharSequence 接口。

  • value[]String 类底层通过 final 类型的 char 数组实现,用于存储字符串内容;
  • hash:用于缓存当前字符串的 hashcode 值,默认为 0

构造器

String 类中重载的构造器很多,其中比较常用的有:

1. 字符串构造

public String(String original) {
    this.value = original.value;
    this.hash = original.hash;
}

用一个 String 类型的对象来初始化 String。实现中直接将源 Stringvaluehash 两个属性直接赋值给目标 String

可以看出,新创建的 String 对象是传入的参数 String 的一个副本。除非确实需要显式得赋值一个字符串对象,否则没有必要使用此构造器来创建一个 String 对象。

2. 字符数组构造

public String(char value[]) {
    this.value = Arrays.copyOf(value, value.length);
}

public String(char value[], int offset, int count) {
    // check bounds
    this.value = Arrays.copyOfRange(value, offset, offset+count);
}

也可以使用一个 char 数组来创建一个 String,实现中通过 Array.copyOfArray.copyOfRange 方法,将原有的字符数组中的内容逐一地赋值到新 String 的字符数组中。

在复制时,即可以复制整个字符数组,也可以通过 offsetcount 两个参数来复制字符数组的一部分。

3. 字节数组构造

public String(byte bytes[], int offset, int length, Charset charset) {
    if (charset == null)
        throw new NullPointerException("charset");
    checkBounds(bytes, offset, length);
    this.value =  StringCoding.decode(charset, bytes, offset, length);
}

使用 byte 数组来构造一个 String,需要将 byte 数组转换为 char 数组。由于在网络传输中,经常需要在 byte[]char[]String 之间相互转化,所以 StringCoding 类提供了一系列重载的构造方法,例如这里的 StringCoding.decode(charset, bytes, offset, length) 方法,它通过指定的 charsetbyte 数组解码为 unicodechar 数组,构造新的 String

还有其他几种类似的构造方法,可以选择性地指定编码格式、解码的第一个字节下标、解码的字节数。

StringCoding.decode 方法中,如果没有指定编码格式,则默认使用 ISO-8859-1 格式进行操作。

常用方法

1. 比较方法

equals 方法用于比较当前对象与传入的字符串对象是否相等。

public boolean equals(Object anObject) {
    // 如果它们是一个对象,肯定为 true
    if (this == anObject) {
        return true;
    }
    // 传入的对象必须是 String
    if (anObject instanceof String) {
        // 将 Object 强转为 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) {
                // 只要有一个不等,即返回 false
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

regionMatches 方法用于比较当前对象与传入的字符串对象的指定区域是否相等。但可以传入一个 boolean 值用于指定是否忽略大小写。

public boolean regionMatches(boolean ignoreCase, int toffset,
        String other, int ooffset, int len) {
    char ta[] = value;
    int to = toffset;
    char pa[] = other.value;
    int po = ooffset;
    if ((ooffset < 0) || (toffset < 0)
            || (toffset > (long)value.length - len)
            || (ooffset > (long)other.value.length - len)) {
        return false;
    }
    while (len-- > 0) {
        char c1 = ta[to++];
        char c2 = pa[po++];
        if (c1 == c2) {
            continue;
        }
        if (ignoreCase) {
            // 根据传入的 boolean 值来判断是否分别转换为大写、小写进行比较
            char u1 = Character.toUpperCase(c1);
            char u2 = Character.toUpperCase(c2);
            if (u1 == u2) {
                continue;
            }
            if (Character.toLowerCase(u1) == Character.toLowerCase(u2)) {
                continue;
            }
        }
        return false;
    }
    return true;
}

compareTo 方法按照字典序比较两个字符串的大小。

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

2. hashcode

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;

        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

String 类计算的 hashcode 的公式如下:

s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]

3. getBytes

public byte[] getBytes(Charset charset) {
    if (charset == null) throw new NullPointerException();
    return StringCoding.encode(charset, value, 0, value.length);
}

使用指定的字符集编码将 String 对象编码成一个 byte 数组。也有不指定编码格式的 getBytes 版本,会默认使用系统的编码方式,例如在中文操作系统中可能使用 GBK,在英文系统中可能使用 IOS-8859-1 格式。

4. indexOf

indexOf 方法返回指定字符在当前 String 对象第一次出现的索引位置。

public int indexOf(int ch, int fromIndex) {
    final int max = value.length;
    if (fromIndex < 0) {
        fromIndex = 0;
    } else if (fromIndex >= max) {
        return -1;
    }
    // 一个完整的 Unicode 字符叫代码点 CodePoint,
    // String 对象以 UTF-16 保存 Unicode 字符,需要两个字符表示一个超大字符集的汉字
    // 如果 cd 参数小于 Unicode 补充代码点的最小值 0x010000
    if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
        final char[] value = this.value;
        // 遍历字符数组
        for (int i = fromIndex; i < max; i++) {
            // 如果当前字符等于 ch,则返回索引 i
            if (value[i] == ch) {
                return i;
            }
        }
        return -1;
    } else {
        return indexOfSupplementary(ch, fromIndex);
    }
}

lastIndexOf 方法返回指定字符在当前 String 对象最后一次出现的索引位置。

public int lastIndexOf(int ch, int fromIndex) {
    if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
        final char[] value = this.value;
        int i = Math.min(fromIndex, value.length - 1);
        for (; i >= 0; i--) {
            if (value[i] == ch) {
                return i;
            }
        }
        return -1;
    } else {
        return lastIndexOfSupplementary(ch, fromIndex);
    }
}

5. replace

replace 方法将当前字符串对象中所有的 oldChar 字符替换为 newChar,返回一个新的 String 对象。

public String replace(char oldChar, char newChar) {
    if (oldChar != newChar) {
        int len = value.length;
        int i = -1;
        char[] val = value;

        // 从索引位置零开始,查找当前字符串中第一次出现字符 oldChar 的索引位置
        while (++i < len) {
            if (val[i] == oldChar) {
                break;
            }
        }
        if (i < len) {
            char buf[] = new char[len];
            // 将 [0,i) 之间的字符直接缓存到 buf 字符数组中
            for (int j = 0; j < i; j++) {
                buf[j] = val[j];
            }
            // 遍历 [i, len - 1] 之间的字符
            while (i < len) {
                char c = val[i];
                // 如果遍历到的字符与 oldChar 相等,则替换,否则保持不变
                buf[i] = (c == oldChar) ? newChar : c;
                i++;
            }
            return new String(buf, true);
        }
    }
    return this;
}

两个方法根据给定的正则表达式替换匹配到的第一个或全部的子字符串。

public String replaceFirst(String regex, String replacement) {
    return Pattern.compile(regex).matcher(this).replaceFirst(replacement);
}

public String replaceAll(String regex, String replacement) {
    return Pattern.compile(regex).matcher(this).replaceAll(replacement);
}

6. valueOf

copyValueOf 方法将指定的字符数组转换成一个 String 对象。

public static String copyValueOf(char data[], int offset, int count) {
    return new String(data, offset, count);
}

valueOf 方法将六种基本数据类型的变量转换成 String 类型。

public static String valueOf(boolean b) {
    return b ? "true" : "false";
}

public static String valueOf(char c) {
    char data[] = {c};
    return new String(data, true);
}

public static String valueOf(int i) {
    return Integer.toString(i);
}

public static String valueOf(long l) {
    return Long.toString(l);
}

public static String valueOf(float f) {
    return Float.toString(f);
}

public static String valueOf(double d) {
    return Double.toString(d);
}

7. concat

将指定的字符串拼接到当期字符串的末尾。

public String concat(String str) {
    int otherLen = str.length();
    if (otherLen == 0) {
        return this;
    }
    int len = value.length;
    // 创建一个 len + otherLen 大小的数组,将 value 拷贝过去
    char buf[] = Arrays.copyOf(value, len + otherLen);
    // 将 str 中的字符拷贝到 buf 中
    str.getChars(buf, len);
    return new String(buf, true);
}

8. startsWith

判断当前字符串从索引位置 toofset 开始是否以指定的前缀字符串开头。

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

判断当前字符串是否以指定的前缀字符串开头或结尾。

public boolean startsWith(String prefix) {
    return startsWith(prefix, 0);
}

public boolean endsWith(String suffix) {
    return startsWith(suffix, value.length - suffix.value.length);
}

9. trim

trim 方法可去掉字符串两端的空格。

public String trim() {
    int len = value.length;
    int st = 0;
    char[] val = value;

    // 找到第一个不为 ' ' 的下标
    while ((st < len) && (val[st] <= ' ')) {
        st++;
    }
    // 找到最后一个不为 ' ' 的下标
    while ((st < len) && (val[len - 1] <= ' ')) {
        len--;
    }
    // 调用 substring 方法截取 [st, len] 之间的字符串
    return ((st > 0) || (len < value.length)) ? substring(st, len) : this;
}

其他方法

// 返回字符串中第 index 个字符
public char charAt(int index) 

// 将 String 转换为 字符数组
public char[] toCharArray() 

// 将 String 按照字符 regex 分为 limit 份
public String[] split(String regex, int limit)

// 转换为大写
public String toUpperCase()

// 转换为小写
public String toLowerCase()