Java源码阅读--String类

209 阅读5分钟

1.概览

String类是Java开发中最最常用的类,所以源码阅读首先选择String类。

2.实现的接口

String类一共实现了三个接口,具体如下:

  • Serializable:标记接口,表示该类可以序列化
  • Comparable: 具有比较能力
  • CharSequence:实现了字节序列操作

3.属性

String类一共有三个属性,具体如下。

private final char value[];
private int hash;
private static final long serialVersionUID;

3.1 value

value是不可变的字符数组。String类的所有操作都是基于对value的操作。从value不可变的性质,可以推出String是不可变的类型。

3.2 hash

String的哈希值。

3.3 serialVersionUID

序列化版本标识。

4.构造方法

String支持默认构造方法,还支持从String构造、byte[]构造、char[]构造、codePoints构造、StringBuffer构造和StringBuffer构造。

4.1 默认构造

String的默认构造得到的是空字符串“”

4.2 从String构造

从String构造,得到的String。两个String共享同一个内存地址。因为String是不修改的,所以可共享同一个内存地址。

4.3 从byte数组构造

使用字节数组构造String,需要指定编码方式,否则,默认使用系统默认编码方式进行构造String。

4.4 从char数组构造

使用char数组构造String时,使用的是深拷贝。深思一下,使用深拷贝可以避免传参进来的字符数组改动导致String内容改变。

4.5 从codePoints构造(使用int数组构造)

使用码点构造String。对于编码方式我不太熟悉,就不展开讲了。

4.6 从StringBuffer构造

StringBuffer是线程安全的,在构造String时,注意用synchronized关键字锁一下。另外,使用StringBuffer构造String,用的是深拷贝。

4.7 从StringBuilder构造

使用的是深拷贝。

5. 长度相关方法

5.1 length方法

返回String的长度

5.2 isEmpty方法

返回String是否为空

6. 索引相关方法

6.1 charAt方法

获取指定index位置的char

6.2 codePointAt方法

获取指定index位置的codePoint

6.3 codePointBefore方法

获取指定index-1位置的codepoint

6.4 codePointCount方法

获取真实字符个数

6.5 offsetByCodePoints方法

从index处,按照代码点进行移动,并返回索引

6.6 getChars方法

从指定位置复制到char数组

6.7 getBytes方法

返回bytes数组

7. 比较方法

7.1 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;
}
  • 先比较地址是否相同,若相同,返回true
  • 在长度相同的情况下比较char是否相同,若不同,返回false;若全部相同,返回true
  • 其他情况返回false

7.2 contentEquals方法

比较String和其他CharSequence内容是否相等的方法

public boolean contentEquals(CharSequence cs) {
    // Argument is a StringBuffer, StringBuilder
    if (cs instanceof AbstractStringBuilder) {
        if (cs instanceof StringBuffer) {
            synchronized(cs) {
               return nonSyncContentEquals((AbstractStringBuilder)cs);
            }
        } else {
            return nonSyncContentEquals((AbstractStringBuilder)cs);
        }
    }
    // Argument is a String
    if (cs instanceof String) {
        return equals(cs);
    }
    // Argument is a generic CharSequence
    char v1[] = value;
    int n = v1.length;
    if (n != cs.length()) {
        return false;
    }
    for (int i = 0; i < n; i++) {
        if (v1[i] != cs.charAt(i)) {
            return false;
        }
    }
    return true;
}
  • 查看cs是否是StringBuilder的实例,若是,使用synchronized关键字锁住,进行内容比较
  • 若cs是StringBuilder的实例,进行内容比较
  • cs是String类型,直接调用equals方法
  • cs是其他charSequence类型,则按一般的方法进行比较

7.3 equalsIgnoreCase方法

忽略大小写,比较字符

public boolean equalsIgnoreCase(String anotherString) {
    return (this == anotherString) ? true
            : (anotherString != null)
            && (anotherString.value.length == value.length)
            && regionMatches(true, 0, anotherString, 0, value.length);
}
  • 比较地址是否相同,相同,返回true
  • 其他情况,需要满足不为null 长度相同 内容忽略大小写一致,则返回true

7.4 regionMatches方法

比较String是否相等,可以忽略大小写比较

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;
    // Note: toffset, ooffset, or len might be near -1>>>1.
    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) {
            // If characters don't match but case may be ignored,
            // try converting both characters to uppercase.
            // If the results match, then the comparison scan should
            // continue.
            char u1 = Character.toUpperCase(c1);
            char u2 = Character.toUpperCase(c2);
            if (u1 == u2) {
                continue;
            }
            // Unfortunately, conversion to uppercase does not work properly
            // for the Georgian alphabet, which has strange rules about case
            // conversion.  So we need to make one last check before
            // exiting.
            if (Character.toLowerCase(u1) == Character.toLowerCase(u2)) {
                continue;
            }
        }
        return false;
    }
    return true;
}
  • 边界校验,不通过直接false
  • 大小写敏感的情况,直接比较字符
  • 大小写不敏感的情况,统一转换成大写处理,由于德语特例,所以,大写校验不过,小写再校验一次

7.5 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;
}
  • 比较相同index的字符,若字符不一致,返回相减结果
  • 若字符都一致,返回长度相减结果

7.6 startsWitch方法

查看从offset处,是否是以prefix开头的字符串

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

8. 不分类了

8.1 hashCode方法

计算String的hashCode

8.2 subString

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

利用了构造函数

8.3 concat方法

public String concat(String str) {
    if (str.isEmpty()) {
        return this;
    }
    int len = value.length;
    int otherLen = str.length();
    char buf[] = Arrays.copyOf(value, len + otherLen);
    str.getChars(buf, len);
    return new String(buf, true);
}

搞一个扩容后的数组,利用getChars填充,再使用构造函数返回

8.4 replace方法

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

        while (++i < len) {
            if (val[i] == oldChar) {
                break;
            }
        }
        if (i < len) {
            char buf[] = new char[len];
            for (int j = 0; j < i; j++) {
                buf[j] = val[j];
            }
            while (i < len) {
                char c = val[i];
                buf[i] = (c == oldChar) ? newChar : c;
                i++;
            }
            return new String(buf, true);
        }
    }
    return this;
}

该方法使用newChar替代所有的oldChar并返回一个新的String。 替换步骤:

  • 判断oldChar和newChar是否是同一个char,是的话,直接返回。不是的话,继续一下步骤。
  • 判断oldChar出现在String中,若没出现,直接返回该String,若出现,继续下一步骤
  • 进行字符串替换

8.5 contains方法

public boolean contains(CharSequence s) {
    return indexOf(s.toString()) > -1;
}

contains方法调用内部的indexof方法,所以重点分析一下indexof方法。

static int indexOf(char[] source, int sourceOffset, int sourceCount,
        char[] target, int targetOffset, int targetCount,
        int fromIndex) {
    // 搜索的位置超过了source子串长度,具体返回啥,看target子串长度
    // target子串长度0,返回source子串长度
    // target子串长度不是0,返回-1,即不合法
    if (fromIndex >= sourceCount) {
        return (targetCount == 0 ? sourceCount : -1);
    }
    // source子串搜索的起始位置若小于0,置为0
    if (fromIndex < 0) {
        fromIndex = 0;
    }
    // 若target子串长度0,则返回搜索的起始位置
    if (targetCount == 0) {
        return fromIndex;
    }
    // 搜索的第一个字符
    char first = target[targetOffset];
    
    // 搜索的合法位置
    // 比如一个串长度9,一个串长度6,搜索的合法位置为[0, 5] [1, 6] [2, 7] [3, 8]
    int max = sourceOffset + (sourceCount - targetCount);

    for (int i = sourceOffset + fromIndex; i <= max; i++) {
        /* Look for first character. */
        if (source[i] != first) {
            while (++i <= max && source[i] != first);
        }

        /* Found first character, now look at the rest of v2 */
        if (i <= max) {
            int j = i + 1;
            int end = j + targetCount - 1;
            for (int k = targetOffset + 1; j < end && source[j]
                    == target[k]; j++, k++);

            if (j == end) {
                /* Found whole string. */
                return i - sourceOffset;
            }
        }
    }
    return -1;
}

这个方法我看了很久,不知道这些参数代表的具体含义。终于,在搜索引擎的帮助下,明白了这个方法的作用,这几个参数代表的东西。

子串一:target字符串中,由targetOffset和targetCount两者确定的target子串

子串二:source字符串中由sourceOffset和sourceCount以及fromIndex确定的子串

判断子串二是否包含子串一,若包含,返回子串二包含的index

其中,

  • source代表比较长的字符串,是在这个串上做搜索操作的
  • sourceOffset,source子串的起始位置
  • sourceCount,source子串的长度
  • target代表比较短的字符串,通常是拿来搜索的串
  • targetOffset,target子串的起始位置
  • targetCount,target子串的长度
  • fromIndex, 表示source子串搜索的起始位置 所以,该函数的具体作用,如上注释所示。

8.6 各种valueOf方法