JDK源码解读第二章:java.lang.string

168 阅读5分钟

java.lang.string

说明:上一章我们阅读了java所有类的父类object,这一章学习下经常用到的string类。

1.String申明

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

这也是一个用 final 声明的常量类,不能被任何类所继承,而且只要一个String对象被创建, 包含在这个对象中的字符序列是不可改变的, 包括该类后续的所有方法都是不能修改该对象的,直至该对象被销毁,平时用到的string对象重新赋值,其实都是新建了一个string对象,把原来对象的指针指向新的对象地址。

  • 实现了 Serializable接口,这是一个序列化标志接口。
  • 还实现了 Comparable 接口,用于比较两个字符串的大小(按顺序比较单个字符的ASCII码),后面会有具体方法实现。
  • 最后实现了 CharSequence 接口,表示是一个有序字符的集合,相应的方法后面也会介绍。

2.String属性

    /**用来存储字符串  */
    private final char value[];
    
    /** 缓存字符串的哈希码 */
    private int hash; // Default to 0
    
    /** 实现序列化的标识 */
    private static final long serialVersionUID = -6849794470754667710L;

3.String构造方法

String的构造方法有很多,先来看看比较常用的

3.1 String()

    public String() {
        this.value = "".value;
    }

无参构造方法
String str =new String();
其实是生成了一个空串,不是null。

3.2 String(String original)

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

String st=new String("str");生成了一个新的对象。

3.3 其他构造方法

    //接收一个char类型的数组,但是方法中使用了数组的copyOf方法,复制
    public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }
    
    
    public String(char value[], int offset, int count) {
    //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) {
               //count=0,返回一个""
                this.value = "".value;
                return;
            }
        }
        // Note: offset or count might be near -1>>>1.
        if (offset > value.length - count) {
            throw new StringIndexOutOfBoundsException(offset + count);
        }
        //复制指定数组的指定范围
        this.value = Arrays.copyOfRange(value, offset, offset+count);
    }
    
    //线程安全的,将一个StringBuffer转化为String
    public String(StringBuffer buffer) {
        synchronized(buffer) {
            this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
        }
    }
    
    //将一个StringBuffer转化为String
    public String(StringBuilder builder) {
        this.value = Arrays.copyOf(builder.getValue(), builder.length());
    }

还有其他很多构造方法就不一一说明了。

4.checkBounds(byte[] bytes, int offset, int length)

    private static void checkBounds(byte[] bytes, int offset, int length) {
        if (length < 0)
            throw new StringIndexOutOfBoundsException(length);
        if (offset < 0)
            throw new StringIndexOutOfBoundsException(offset);
        if (offset > bytes.length - length)
            throw new StringIndexOutOfBoundsException(offset + length);
    }

用于边界检查字节数组的公共专用实用方法

5.length()

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

返回字符串长度,字符串长度是否为0,"".length()长度为0,所以此方法不能用于判断空字符串。

5.charAt(int index)

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

很明显是返回传入下标所在的字符。

6.equals(Object anObject)

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

String 类重写了object的 equals 方法,需要比较的是组成字符串的每一个字符是否相同,如果都相同则返回true,否则返回false。

7.equalsIgnoreCase(String anotherString)

   public boolean equalsIgnoreCase(String anotherString) {
        return (this == anotherString) ? true
                : (anotherString != null)
                && (anotherString.value.length == value.length)
                && regionMatches(true, 0, anotherString, 0, value.length);
    }

比较两个字符串是否相等,不区分大小写。

8.compareTo(String anotherString)

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

源码也很好理解,该方法是按字母顺序比较两个字符串,是基于字符串中每个字符的 Unicode 值。当两个字符串某个位置的字符不同时,返回的是这一位置的字符 Unicode 值之差,当两个字符串都相同时,返回两个字符串长度之差。

9.compareToIgnoreCase(String str)

    public int compareToIgnoreCase(String str) {
        return CASE_INSENSITIVE_ORDER.compare(this, str);
    }
    
    public static final Comparator<String> CASE_INSENSITIVE_ORDER
                                         = new 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; }
    }

compareToIgnoreCase() 方法在 compareTo 方法的基础上忽略大小写,我们知道大写字母是比小写字母的Unicode值小32的,底层实现是先都转换成大写比较,然后都转换成小写进行比较。

10.regionMatches(int toffset, String other, int ooffset,int len)

    public boolean regionMatches(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) {
            if (ta[to++] != pa[po++]) {
                return false;
            }
        }
        return true;
    }
    
     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;
    }

方法用于检测两个字符串在一个区域内是否相等

  • ignoreCase -- 如果为 true,则比较字符时忽略大小写。
  • toffset -- 此字符串中子区域的起始偏移量。
  • other -- 字符串参数。
  • ooffset -- 字符串参数中子区域的起始偏移量。
  • len -- 要比较的字符数。

11.startsWith(String prefix, int 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;
    }
    
    public boolean startsWith(String prefix) {
        return startsWith(prefix, 0);
    }

startsWith() 方法用于检测字符串是否以指定的前缀开始。

  • prefix -- 前缀。
  • toffset -- 字符串中开始查找的位置。

12.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 算法很简单,主要就是中间的 for 循环,计算公式如下:
s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
s 数组即源码中的 val 数组,也就是构成字符串的字符数组。这里有个数字 31 ,为什么选择31作为乘积因子,而且没有用一个常量来声明?主要原因有两个:
①、31是一个不大不小的质数,是作为 hashCode 乘子的优选质数之一。
②、31可以被 JVM 优化,31 * i = (i << 5) - i。因为移位运算比乘法运行更快更省性能。

13.indexOf(int ch)

    public int indexOf(int ch) {
        return indexOf(ch, 0);//从第一个字符开始搜索
    }
    
    public int indexOf(int ch, int fromIndex) {
    final int max = value.length;//max等于字符的长度
    if (fromIndex < 0) {//指定索引的位置如果小于0,默认从 0 开始搜索
        fromIndex = 0;
    } else if (fromIndex >= max) {
        //如果指定索引值大于等于字符的长度(因为是数组,下标最多只能是max-1),直接返回-1
        return -1;
    }

    if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) {//一个char占用两个字节,如果ch小于2的16次方(65536),绝大多数字符都在此范围内
        final char[] value = this.value;
        for (int i = fromIndex; i < max; i++) {//for循环依次判断字符串每个字符是否和指定字符相等
            if (value[i] == ch) {
                return i;//存在相等的字符,返回第一次出现该字符的索引位置,并终止循环
            }
        }
        return -1;//不存在相等的字符,则返回 -1
    } else {//当字符大于 65536时,处理的少数情况,该方法会首先判断是否是有效字符,然后依次进行比较
        return indexOfSupplementary(ch, fromIndex);
    }
}

indexOf(int ch) ch是表示字符的 Unicode 值 indexOf(String str) 从前往后返回第一个字符所在索引。
lastIndexOf(int ch) ch是表示字符的 Unicode 值 lastIndexOf(String str) 从后往前面走的第一个字符所在索引。

14.substring(int beginIndex)

    public String substring(int beginIndex) {
     if (beginIndex < 0) {//如果索引小于0,直接抛出异常
        throw new StringIndexOutOfBoundsException(beginIndex);
     }
    int subLen = value.length - beginIndex;//subLen等于字符串长度减去索引
    if (subLen < 0) {//如果subLen小于0,也是直接抛出异常
        throw new StringIndexOutOfBoundsException(subLen);
    }
    //1、如果索引值beginIdex == 0,直接返回原字符串
    //2、如果不等于0,则返回从beginIndex开始,一直到结尾
    return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
    }

substring(int beginIndex) 返回一个从索引 beginIndex 开始后面所有的字符串。
substring(int beginIndex, int endIndex):返回一个从索引 beginIndex 开始,到 endIndex 结尾的子字符串。

15.concat(String str)

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

该方法通过 Arrays 工具类的copyOf方法创建一个新的字符数组,长度为原字符串和要拼接的字符串之和,前面填充原字符串,后面为空。接着在通过 getChars 方法将要拼接的字符串放入新字符串后面为空的位置。getChars为本地方法。最后new一个新的字符串为拼接结果。

注意:返回值是 new String(buf, true),也就是重新通过 new 关键字创建了一个新的字符串,原字符串是不变的。这也可以看出一个String对象被创建, 包含在这个对象中的字符序列是不可改变的。

16.replace(char oldChar, char newChar)

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

第一步,判断oldchar存在于字符数组中,存在跳出while循环,并得到改字符第一次出现的位置, 如果不存在,i的值会==len,下面的判断,就不会再进行了,也就是说,这个字符串中不包含要替换的字符,返回原来的字符串。
第二步,从开始位置一直往后查找到和替换字符相同的字符,用新的字符代替,最后返回一个新的字符串。

17.contains(CharSequence s)

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

indexOf 找到字符串对应的下标,如果大于-1即包含,反之则不包含。

18.contains(CharSequence s)

     public String[] split(String regex, int limit) {
        char ch = 0;
        if (((regex.value.length == 1 && //判断参数长度是否为1
             ".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) || //判断参数不在特殊符号".$|()[{^?*+\\"中 
             (regex.length() == 2 && //判断参数长度是否为2
              regex.charAt(0) == '\\' &&  \\第一位为转义符"\\"
              (((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 && //第二位不是0-9之间 '0'转换为int为48 '9'转换为int为57 
              ((ch-'a')|('z'-ch)) < 0 && //判断不在 a-z之间
              ((ch-'A')|('Z'-ch)) < 0)) && //判断不在A-Z之间
            (ch < Character.MIN_HIGH_SURROGATE || 
             ch > Character.MAX_LOW_SURROGATE))  //判断分隔符不在特殊符号中
        {
            int off = 0;//当前索引
            int next = 0;//下一个分割符出现的索引
            boolean limited = limit > 0;//只分割前limit份还是全部分割,limit=0代表全部分割
            ArrayList<String> list = new ArrayList<>();//创建一个集合,用于存放切割好的子串
            while ((next = indexOf(ch, off)) != -1) {//判断是否包含下个分隔符,如果有则进入循环
                if (!limited || list.size() < limit - 1) {//判断是全部分割或当前分割次数小于总分割次数
                    list.add(substring(off, next));//切割当前索引到下一个分隔符之间的字符串并添加到list中
                    off = next + 1; //继续切割下一下子串
                } else {
                    list.add(substring(off, value.length));//切割当前索引到字符串结尾的子字符串并添加到list
                    off = value.length;//将当前索引置为字符串长度
                    break;//结束循环
                }
            }
            if (off == 0) //如果找不到分隔符则返回只有本字符串的数组
                return new String[]{this};

            if (!limited || list.size() < limit)//如果是全部分割,或者没有达到分割数,则追加最后一项
                list.add(substring(off, value.length));

            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);//其它情况用正则的切割规则去切割
    }