天天看点

String 类源码解读定义属性内部类构造方法其他方法

String 虽然不是几大基础数据类型之一,但它也是我们很常见的数据类型。最常见听到最多的就是 String 是不可变的。

定义

String 类的定义语句如下:

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

可以了解到以下几点:

  1. 被 final 关键字修饰过,所以该类不可以被继承。
  2. 该类实现了 CharSequence 接口(可以看 CharSequence 接口解读),并实现了 Comparable 接口用于对两个实例化对象比较大小。java.io.Serializable 这个序列化接口,仅用于标识序列化,后续分析暂时是不考虑这个接口。

属性

private final char value[];
private int hash; // Default to 0
private static final long serialVersionUID = -6849794470754667710L;
public static final Comparator<String> CASE_INSENSITIVE_ORDER
        = new CaseInsensitiveComparator();
           
  1. 在 String 内部,是利用 value[] 这个 char 类型数组来存储 String 内容的,并且这个数组是被 final 修饰的,表示这个值初始化后是无法改变的。
  2. 而 hash 它是 String 实例化 hashcode 的缓存,因为我们知道 String 经常拿来被比较,而比较就需要进行 hashcode 值得比较,如果每次比较都重新计算一次 hashcode 的值就很麻烦,所以缓存下来是比较好的操作。
  3. 序列值用在序列化的时候。
  4. CASE_INSENSITIVE_ORDER 其本质是持有 String 静态内部类,用于忽略大小写得比较两个字符串。

内部类

String 类里有一个静态内部类 CaseInsensitiveComparator:

private static class CaseInsensitiveComparator
            implements Comparator<String>, java.io.Serializable {
        // use serialVersionUID from JDK 1.2.2 for interoperability
        private static final long serialVersionUID = 8575799808933029326L;
        public int compare(String s1, String s2) {
            int n1 = s1.length();
            int n2 = s2.length();
            int min = Math.min(n1, n2);
            for (int i = 0; i < min; i++) {
                char c1 = s1.charAt(i);
                char c2 = s2.charAt(i);
                if (c1 != c2) {
                    c1 = Character.toUpperCase(c1);
                    c2 = Character.toUpperCase(c2);
                    if (c1 != c2) {
                        c1 = Character.toLowerCase(c1);
                        c2 = Character.toLowerCase(c2);
                        if (c1 != c2) {
                            // No overflow because of numeric promotion
                            return c1 - c2;
                        }
                    }
                }
            }
            return n1 - n2;
        }
        /** Replaces the de-serialized object. */
        private Object readResolve() { return CASE_INSENSITIVE_ORDER; }
    }
           

这个类主要实现的是 compare 方法,实现的功能也很容易看出来就是忽略字符大小写比较两个 String 对象。

这里有两个问题:

  1. 看代码 14 - 21 行,为什么字符转换为大写之后不相等还要转换为小写再比较一次?不是有点多余?

    是因为 Java 为了针对 Georgian(格鲁吉亚)字母表奇怪的大小写转换规则而专门又增加了一步判断。

  2. 在 String 中已经有了一个 ompareTo 的方法,为什么还要有一个 CaseInsensitiveComparator 的内部静态类呢?

    看过一些人的解释,感觉主要的思路在于代码复用吧。首先不区分大小写比较字符串是一个比较常见的需求。简单对比的话,只需要调用 String. compareTo(another)。如果用于不区分大小写排序,则需要一个不区分大小写字符的 Comparator 实例。而实际中 String 类中提供的 compareToIgnoreCase 方法其实就是调用这个内部类里面的方法实现的。

    至于私有静态内部类,是因为这个实例不需要实例化多个,它是单例的,用的时候只需要用类的 CASE_INSENSITIVE_ORDER 属性即可。

构造方法

String 支持多种初始化方法,包括接收 String、char[]、byte[]、StringBuffer 等多种参数类型的初始化方法。展示两个作为样例:

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

我们可以看到不管传递什么参数类型本质上就是将接收到的参数传递给全局变量 value[]。char[] 参数的构造方法调用了 Arrays.copyOf 方法来进行拷贝,也说明了 String 的不可变性。

其他方法

length()、isEmpty()、charAt()方法

因为 String 内部是 char 数组,所以这些函数的操作都是通过内部调用数组的方法来实现。

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

equals()、hashcode()、contentEquals()方法

equals() 和 hashCode() 两个方法服务于字符串比较。

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;
            // 比较各自String的字符数组的长度是否相同
            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 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;
    }
           

除 equals() 外,还有只比较内容的 contentEquals() 方法。这个方法主要是用来比较String和StringBuffer或者StringBuild的内容是否一样。

// 传入参数为CharSequence,说明了String、StringBuffer和StringBuild都实现了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);
        }
        // for循环来进行判断两字符串是否内容相同
        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;
    }
           

replace() 方法

替换单个字符。主要是这个方法最后会返回一个新的字符串,而不是改原来的 String,所以使用的时候记得用 String 来接收。

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) {
       //定义一个新的字符数组来装String的值。然后这buf[]里面进行替换
                char buf[] = new char[len];
                for (int j = 0; j < i; j++) {
                    buf[j] = val[j];
                }
        //替换的方法。逐个比较,相同则替换成新的,不同则还是c.三目运算
                while (i < len) {
                    char c = val[i];
                    buf[i] = (c == oldChar) ? newChar : c;
                    i++;
                }
          //最后new了一个String返回。
                return new String(buf, true);
            }
        }
        return this;
    }
           

startsWith() 方法

判断当前字符串是否以某一段其他字符串开始的,通过一个 while 来循环比较实现。

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;
        
        if ((toffset < 0) || (toffset > value.length - pc)) {
            return false;
        }
        while (--pc >= 0) {
            if (ta[to++] != pa[po++]) {
                return false;
            }
        }
        return true;
    }
           

indexOf() 方法

获得某个字符在 String 的字符数组里面的第一次出现的下标。

public int indexOf(int ch, int fromIndex) {
        final int max = value.length;
        if (fromIndex < 0) {
            fromIndex = 0;
        } else if (fromIndex >= max) {
            // Note: fromIndex might be near -1>>>1.
            return -1;
        }
    
        if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
            // handle most cases here (ch is a BMP code point or a
            // negative value (invalid code point))
            final char[] value = this.value;
            for (int i = fromIndex; i < max; i++) {
                if (value[i] == ch) {
                    return i;
                }
            }
            return -1;
        } else {
            return indexOfSupplementary(ch, fromIndex);
        }
    }
           

我们可以看到 indexOf 的参数是 int 而不是 char 类型。因为 char 存储的值通常都是比 0x010000 小的,就是 BMP 类型的字符,而当比这个值大的时候,就是增补字符了,但也是有效的字符。所以在代码的第 10 行也会看到 ch < Character.MIN_SUPPLEMENTARY_CODE_POINT 来保证这个是有效的字符。

所以这里的 int 是目标字符的 Unicode 值,放单个字符的话也会转成字符在 Unicode 编码中对应的代码值。

concat() 方法

它可以将 str 拼接到当前字符串后面。都是都底层的 arraycopy 方法来实现的。也是建一个新的字符串返回。

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