jdk源码——java.lang.String(1)

java.lang.String


开始整理java.lang.String的源码,String的类在实际开发中使用非常的普遍,基本每一位java程序员,都有使用过String类以及里面的各种方法。

1.String类的结构图

String类的构造方法相当之多,方法也很多,慢慢全都整理出来,为了便于记忆,会把重要的后面加上*以表示重要。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
第一反应,我去,居然这么多方法,加油。

2.String 类详细介绍

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

从源码上看CaseInsensitiveComparator这个方法重写了Comparator接口中的compare方法,compare在Comparator并没有实现,所以在String类中实现了这个排序的方法,下面来分析这部分实现过程。

        public int compare(String s1, String s2) {
        	// 获取传入字符串s1的长度
            int n1 = s1.length();
            // 获取传入字符串s2的长度
            int n2 = s2.length();
            // 获取最小的长度
            int min = Math.min(n1, n2);
            // 用最小的长度作为最大值,进行循环
            for (int i = 0; i < min; i++) {
            	// 返回s1的每一个字符的ASCII码
                char c1 = s1.charAt(i);
                // 返回s2的每一个字符的ASCII码
                char c2 = s2.charAt(i);
                // 判断字符是否相等
                if (c1 != c2) {
                	// 将c1字符变成大写的
                    c1 = Character.toUpperCase(c1);
                    // 将c2字符变成大写的
                    c2 = Character.toUpperCase(c2);
                    // 比较是否相等
                    if (c1 != c2) {
                    	// 将c1字符变成小写的
                        c1 = Character.toLowerCase(c1);
                        // 将c2字符变成小写的
                        c2 = Character.toLowerCase(c2);
                        // 比较是否相当
                        if (c1 != c2) {
                            // No overflow because of numeric promotion
                            return c1 - c2;
                        }
                    }
                }
            }
            return n1 - n2;
        }

这里面有一个小小的讨论点,就算是部分大小写的比较是否相当,那为什么变成大写之后比较不同仍然要再一次变成小写对比一次呢?
有些Unicode字符的小写不同,但大写形式相同。例如,希腊字母Sigma-它有两个小写形式(σ和只在单词末尾使用),但只有一个大写形式(Σ)。java为了适应所有的语言,所以会进行两次比较。

2.2 String构造函数

2.2.1 String()

无参构造方法,该构造方法会创建空的字符序列。
看一下源码

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

这个没啥好解释的,直接来个例子一看就懂。

	String str = new String();

创建了一个空的字符串

2.2.2 String(String original)

参数为字符串类型,这个方法一般使用的也不是很多
看一下源码

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

这个方法除了初始化了String类中的value,同时获取了这个String的hashCode
写一个简单的应用例子

	String str = new String("str");

创建一个"str"的字符串

2.2.3 String(char value[])

参数为 char 数组类型
源码:

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

使用字符串数组来创建String时,会使用Arrays.copyOf方法,将原有的字符串数组中的内容逐一复制到String的字符串数组中。

写一个小例子

		char[] ch = {65, 66};
		String c = new String(ch);
		System.out.println(c);

输出结果是:

AB
2.2.4 String(char value[], int offset, int count)

参数还是 char 数组类型,只不过规定了开始的位置和获取多少位数
源码:

    public String(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) {
                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);
    }

当开始位置小于9,获取位数小于0或者开始位置大于字符串长度-获取长度时,都会抛出StringIndexOutOfBoundsException异常。之后会调用Arrays.copyOfRange(value, offset, offset+count);方法,来将范围内的字符串的内容复制或到String的字符串中。
写个小例子:

		char[] ch = {65, 66, 67, 68, 69, 70, 71, 72, 73};
		String b = new String(ch);
		System.out.println(b);
		String c = new String(ch, 2, 2);
		System.out.println(c);

输出结果为:

ABCDEFGHI
CD

感觉这个方法的应用真的非常小,这个方法以前居然从来没有注意过。

2.2.5 String(int[] codePoints, int offset, int count)

参数变成了int数组类型,只不过规定了开始的位置和获取多少位数
源码:

    /**
     * Allocates a new {@code String} that contains characters from a subarray
     * of the <a href="Character.html#unicode">Unicode code point</a> array
     * argument.  The {@code offset} argument is the index of the first code
     * point of the subarray and the {@code count} argument specifies the
     * length of the subarray.  The contents of the subarray are converted to
     * {@code char}s; subsequent modification of the {@code int} array does not
     * affect the newly created string.
     *
     * @param  codePoints
     *         Array that is the source of Unicode code points
     *
     * @param  offset
     *         The initial offset
     *
     * @param  count
     *         The length
     *
     * @throws  IllegalArgumentException
     *          If any invalid Unicode code point is found in {@code
     *          codePoints}
     *
     * @throws  IndexOutOfBoundsException
     *          If the {@code offset} and {@code count} arguments index
     *          characters outside the bounds of the {@code codePoints} array
     *
     * @since  1.5
     */
    public String(int[] codePoints, int offset, int count) {
        if (offset < 0) {
            throw new StringIndexOutOfBoundsException(offset);
        }
        if (count <= 0) {
            if (count < 0) {
                throw new StringIndexOutOfBoundsException(count);
            }
            if (offset <= codePoints.length) {
                this.value = "".value;
                return;
            }
        }
        // Note: offset or count might be near -1>>>1.
        if (offset > codePoints.length - count) {
            throw new StringIndexOutOfBoundsException(offset + count);
        }

        final int end = offset + count;

        // Pass 1: Compute precise size of char[]
        int n = count;
        for (int i = offset; i < end; i++) {
            int c = codePoints[i];
            if (Character.isBmpCodePoint(c))
                continue;
            else if (Character.isValidCodePoint(c))
                n++;
            else throw new IllegalArgumentException(Integer.toString(c));
        }

        // Pass 2: Allocate and fill in char[]
        final char[] v = new char[n];

        for (int i = offset, j = 0; i < end; i++, j++) {
            int c = codePoints[i];
            if (Character.isBmpCodePoint(c))
                v[j] = (char)c;
            else
                Character.toSurrogates(c, v, j++);
        }

        this.value = v;
    }

看源码可以知道,这个方法是java1.5加进去的,忽略的看一下,发现这个方法和String(char value[], int offset, int count)的功能好像是一样的,那不一样体现在哪里呢?
首先我们来看两个方法的具体内容
1.Character.isBmpCodePoint(codePoint)

    public static boolean isBmpCodePoint(int codePoint) {
        return codePoint >>> 16 == 0;
        // Optimized form of:
        //     codePoint >= MIN_VALUE && codePoint <= MAX_VALUE
        // We consistently use logical shift (>>>) to facilitate
        // additional runtime optimizations.
    }

传入的数字按位右移16位,这代表了什么?
首先我们知道char是16bits的,代码点在U+0000 — U+FFFF之内到是可以用一个char完整的表示出一个字符。但代码点在U+FFFF之外的,一个char无论如何无法表示一个完整字符。这样用char类型来获取字符串中的那些代码点在U+FFFF之外的字符就会出现问题。
而java为了解决这样的问题,就出现了Unicode编码字符集的增补字符,而这些增补字符的处理逻辑与原来的不同,所以在用int[]传入的时候,要把符合U+0000 — U+FFFF之间的字符按照char来处理,其他的则按照新的方式来处理。

2.Character.isValidCodePoint(codePoint)

    public static boolean isValidCodePoint(int codePoint) {
        // Optimized form of:
        //     codePoint >= MIN_CODE_POINT && codePoint <= MAX_CODE_POINT
        int plane = codePoint >>> 16;
        return plane < ((MAX_CODE_POINT + 1) >>> 16);
    }

所以这段代码则是判断传入的数字是否是增补码,就是在U+10000—U+10FFFF之间的字符。

看到这里基本就理解了String(int[] codePoints, int offset, int count)与String(char value[], int offset, int count)功能相同,只不过是增加了一些Unicode编码的范围。

2.2.6 String(byte ascii[], int hibyte, int offset, int count)和String(byte ascii[], int hibyte)

这两个方法目前已经过时了,暂时就不去学习了。

2.2.7 String(byte bytes[], int offset, int length, String charsetName)和String(byte bytes[], int offset, int length, Charset charset)

参数变成了byte数组类型,还是规定了开始的位置和获取多少位数,同时规定编码格式
源码:

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

还有StringCoding.decode(String charsetName, byte[] ba, int off, int len)的源码

    static char[] decode(String charsetName, byte[] ba, int off, int len)
        throws UnsupportedEncodingException
    {
        StringDecoder sd = deref(decoder);
        String csn = (charsetName == null) ? "ISO-8859-1" : charsetName;
        if ((sd == null) || !(csn.equals(sd.requestedCharsetName())
                              || csn.equals(sd.charsetName()))) {
            sd = null;
            try {
                Charset cs = lookupCharset(csn);
                if (cs != null)
                    sd = new StringDecoder(cs, csn);
            } catch (IllegalCharsetNameException x) {}
            if (sd == null)
                throw new UnsupportedEncodingException(csn);
            set(decoder, sd);
        }
        return sd.decode(ba, off, len);
    }

我们可以看到传入的参数根据指定的编码格式进行解析,具体的解析方式将会在StringCoding这个类中具体去学习。
这里有一个很有意思的小现象,从源码中我们可以知道charsetName这个参数是不能为null的,如果为null则会抛出异常NullPointerException,但是在decode的源码中,charsetName如果为null则会被赋予一个缺省值"ISO-8859-1",这样看上去貌似在String(byte bytes[], int offset, int length, String charsetName)中去做是否为null的判断有些多余了。
String(byte bytes[], int offset, int length, Charset charset)方法则是直接传入了字符集,规则是一样的。

2.2.8 String(byte bytes[], String charsetName)和public String(byte bytes[], Charset charset)

源码:

    public String(byte bytes[], String charsetName)
            throws UnsupportedEncodingException {
        this(bytes, 0, bytes.length, charsetName);
    }
	public String(byte bytes[], Charset charset) {
        this(bytes, 0, bytes.length, charset);
    }

就是调用了2.2.7的方法,缺省了截位,获取全部的内容。

2.2.9 String(byte bytes[], int offset, int length)

源码:

    public String(byte bytes[], int offset, int length) {
        checkBounds(bytes, offset, length);
        this.value = StringCoding.decode(bytes, offset, length);
    }

此方法,调用了2.2.7中的缺省了编码格式,默认会使用"ISO-8859-1"进行编码。

2.2.10 String(byte bytes[])

源码:

	public String(byte bytes[]) {
        this(bytes, 0, bytes.length);
    }

参照2.2.9

2.2.11 String(StringBuffer buffer)和String(StringBuilder builder)

参数为StringBuffer 或 StringBuilder
源码:

    public String(StringBuffer buffer) {
        synchronized(buffer) {
            this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
        }
    }
    public String(StringBuilder builder) {
        this.value = Arrays.copyOf(builder.getValue(), builder.length());
    }

感觉这个真的是很少用到了

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

代码非常的简洁,出现数组越界就会报错,然而有点尴尬,我接受的代码编程的规范思想是,能不抛出异常的情况,就不抛出异常,所以一般都会去判断字符串是不是可以截位才会去截位,对于这样的写法一般是不允许的,不过看着这个代码,感觉这麽写再捕获一下,逼格好高的啊。

2.4 length() *

返回一个字符串的长度
源码:

	public int length() {
        return value.length;
    }

源码很好理解,也经常用到

2.5 isEmpty()

判读字符串长度是否为0
源码:

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

源码中可以看出来,如果这个字符串是null的话,会抛出NullPointerException,只有字符串为""时才会返回ture。
此方法一般不经常使用,因为在StringUnit中重写了isEmpty(),这时的方法将支持null的判断,在代码中使用更加的灵活。

2.6 代码点

引入这个概念的原因是因为在早期java使用的时候,char采用UCS-2编码是一种淘汰的UTF-16编码,最多65536种形态,而现如今unicode拥有11万字符的需求,java只好对后来新增的unicode字符用2个char拼出1个unicode字符。这样就会导致String中char的数量不等于unicode字符的数量。所以需要引入代码点的概念,在精确统计字符数量等问题。

2.6.1 charAt(int index)

用于返回指定索引处的字符。索引范围为从 0 到 length() - 1
源码:

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

如果索引范围为超过 0 到 length() - 1,就会抛出异常StringIndexOutOfBoundsException

2.6.2 codePointAt(int index)

用于返回指定索引处的字符的ACSII码。索引范围为从 0 到 length() - 1
源码:

    public int codePointAt(int index) {
        if ((index < 0) || (index >= value.length)) {
            throw new StringIndexOutOfBoundsException(index);
        }
        return Character.codePointAtImpl(value, index, value.length);
    }

如果索引范围为超过 0 到 length() - 1,就会抛出异常StringIndexOutOfBoundsException

2.6.3 codePointBefore(int index)

用于返回指定索引处前一个的字符的ACSII码。索引范围为从 1 到 length()
源码:

    public int codePointBefore(int index) {
        int i = index - 1;
        if ((i < 0) || (i >= value.length)) {
            throw new StringIndexOutOfBoundsException(index);
        }
        return Character.codePointBeforeImpl(value, index, 0);
    }

如果索引范围为超过 1 到 length(),就会抛出异常StringIndexOutOfBoundsException

2.6.4 codePointCount(int beginIndex, int endIndex)

用于返回指定索引处的字符长度。规定开始字符索引和结束字符索引
beginIndex–开始索引位置
endIndex–结束索引位置
源码:

    public int codePointCount(int beginIndex, int endIndex) {
        if (beginIndex < 0 || endIndex > value.length || beginIndex > endIndex) {
            throw new IndexOutOfBoundsException();
        }
        return Character.codePointCountImpl(value, beginIndex, endIndex - beginIndex);
    }

其实这个用法和length一样的,不同的就是这个能识别一些特殊的字符,也就是后来的unicode字符。

2.6.5 offsetByCodePoints(int index, int codePointOffset)

用于返回指定索引处的字符长度。规定开始字符索引和检索长度
index–开始索引位置
codePointOffset–获取字符长度
源码:

    public int offsetByCodePoints(int index, int codePointOffset) {
        if (index < 0 || index > value.length) {
            throw new IndexOutOfBoundsException();
        }
        return Character.offsetByCodePointsImpl(value, 0, value.length,
                index, codePointOffset);
    }

对于编码在实际使用中,特殊的字符并没有经常遇到,所以对于这样的编码暂时不做深入的研究,等学习到Character类的时候,在做探讨。

2.7 getChars()

getChars() 方法将字符从字符串复制到目标字符数组,分为两种方法,分别是getChars(int srcBegin, int srcEnd, char dst[], int dstBegin)和getChars(char dst[], int dstBegin)。

2.7.1 getChars(char dst[], int dstBegin)

拷贝一个字符串到dst中,规定了开始位置。
源码:

    void getChars(char dst[], int dstBegin) {
        System.arraycopy(value, 0, dst, dstBegin, value.length);
    }

注意这个方法不是public的。

2.7.2 getChars(int srcBegin, int srcEnd, char dst[], int dstBegin)

将字符从字符串复制到目标字符数组。
srcBegin – 字符串中要复制的第一个字符的索引。
srcEnd – 字符串中要复制的最后一个字符之后的索引。
dst – 目标数组。
dstBegin – 目标数组中的起始偏移量。
源码:

    public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
        if (srcBegin < 0) {
            throw new StringIndexOutOfBoundsException(srcBegin);
        }
        if (srcEnd > value.length) {
            throw new StringIndexOutOfBoundsException(srcEnd);
        }
        if (srcBegin > srcEnd) {
            throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
        }
        System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
    }

规定了一下必要的检查,而复制的工作是由System.arraycopy来完成的,这个方法不是在java中实现的。
这里面有一个问题,就是起始偏移量是什么?
上述目标数组中的起始偏移量。也就是说dst数组中在第几个下标开始进行复制操作。

2.8 getBytes()

该方法是获得字符串的默认或者指定编码格式的字节数组。

2.8.1 getBytes()

String的getBytes()方法是得到一个操作系统默认的编码格式的字节数组
源码:

    public byte[] getBytes() {
        return StringCoding.encode(value, 0, value.length);
    }

小例子:

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		String aa = "若勖";
		System.out.println(aa.getBytes());
	}

输出结果为:

[B@69222c14

至于这个编码有什么用呢?一般在做I/O,或者web交互的时候会需要以Byte的方式进行传输,这个时候就需要规定一个统一的规则,来进行转换。

2.8.2 getBytes(String charsetName) *

得到一个指定的编码格式的字节数组。
这个方法在实际应用中,会经常用到。
源码:

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

根据指定的编码格式进行转换,这里写一个小例子:

	public static void main(String[] args) throws UnsupportedEncodingException {
		// TODO Auto-generated method stub
		String aa = "若勖";
		System.out.println(aa.getBytes("GBK"));
		System.out.println(aa.getBytes("UTF-8"));
		System.out.println(aa.getBytes("ISO8859-1"));
		System.out.println(aa.getBytes("unicode"));
	}

输出结果:

[B@606d8acf
[B@782830e
[B@33833882
[B@5680a178

可见使用不同的编码格式进行转换,会得到不同的字节数组。

2.8.3 getBytes(Charset charset)

不指定编码格式,直接指定编码名称所对应的字符集
源码:

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

用法与getBytes(String charsetName) 相同,只是传入的参数为字符集而不是字符的名称

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

从源码中可以看到,首先是去比较地址是否相同,如果相同则直接返回true,如果地址不相同,则去判断传入参数是不是String类型,是的话将其转换为String,然后获取字符串的长度,如果长度相同的话,则去比较每一位字符的char是否相当。如果都相当,则返回true。
注意:如果是null.equals()会抛出空指针。

2.10 contentEquals(StringBuffer sb)

contentEquals可以用来比较String对象内容序列的异同,但是与equals还是有一定的差异,主要差异体现在String的equals方法只有在另一个对象是String的情况下才可能返回true,
而contentEquals只要求另一个对象是CharSequence或其子类的对象。
源码:

    public boolean contentEquals(StringBuffer sb) {
        return contentEquals((CharSequence)sb);
    }
    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;
    }
    private boolean nonSyncContentEquals(AbstractStringBuilder sb) {
        char v1[] = value;
        char v2[] = sb.getValue();
        int n = v1.length;
        if (n != sb.length()) {
            return false;
        }
        for (int i = 0; i < n; i++) {
            if (v1[i] != v2[i]) {
                return false;
            }
        }
        return true;
    }

三部分源码放在一次来学习,如果接受到的参数是StringBuffer或者StringBuilder的话,则会调用nonSyncContentEquals方法,先获取sb对应的char,然后在进行比较。
如果传入参数是String,则直接调用equals方法进行比较,如果传入参数是CharSequence,则直接用入参的字符与String的字符进行比较。

2.11 equalsIgnoreCase(String anotherString)

这个也是比较家族中的一个方法,他和equals的区别就是equalsIgnoreCase是不区分大小写的。
源码:

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

第二部分

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

有点类似compare的方法,先比较大写再比较小写。

2.12 compareTo(String anotherString) *

这个方法也是经常用到,用于比较字符串的大小。
其实现实使用中,我一直觉得比较字符串的大小没有什么意义,一般不单独使用,会和steam流一起用于list的排序。
源码:

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

源码也很好理解,逐位去比较每个字符的的ASCII码的大小。

2.13 regionMatches()

用于检测两个字符串在一个区域内是否相等。
共有两个方法
regionMatches(int toffset, String other, int ooffset, int len)
和regionMatches(boolean ignoreCase, int toffset, String other, int ooffset, int len)
区别在于是否需要区分大小写。
源码:
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;
    }

首先将需要比较的字符转换为字符数组,之后按照开始比较的位置,对每一个字符进行比较,发现有不相等的字符,则返回false

regionMatches(boolean ignoreCase, int toffset, String other, int ooffset, int len)

	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是,则是不区分大小写,会进行字符的大小写转换对比,方式参考compare。

2.14 startsWith相关方法

startsWith也是用于比较的字符串的方式,用于判断一个字符串是否以一特定的字符串为开始字符串,相关包含startsWith和endWith,endsWith则是startsWith的一种特殊情况。
源码:

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

从源码中可以看是,该方法存在一个toffset偏移量,就是说,他可以规定从第几个字符开始作为开始字符去匹配。
与改方法相关的方法还有两个,分别是:
startsWith(String prefix)默认的偏移量为0

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


endsWith(String suffix)

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

偏移量为被比较字符串的长度-比较字符串的长度。

2.15 hashCode()

这部分重写了Object的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

在String类中有个私有实例字段hash表示该串的哈希值,在第一次调用hashCode方法时,字符串的哈希值被计算
并且赋值给hash字段,之后再调用hashCode方法便可以直接取hash字段返回。

String类中的hashCode计算方法还是比较简单的,就是以31为权,每一位为字符的ASCII值进行运算,用自然溢出
来等效取模。

哈希计算公式可以计为s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
关于为什么取31为权,可以参考StackOverflow上的这个问题
主要是因为31是一个奇质数,所以31*i=32*i-i=(i<<5)-i,这种位移与减法结合的计算相比一般的运算快很多。

字符串哈希可以做很多事情,通常是类似于字符串判等,判回文之类的。
但是仅仅依赖于哈希值来判断其实是不严谨的,除非能够保证不会有哈希冲突,通常这一点很难做到。

就拿jdk中String类的哈希方法来举例,字符串"gdejicbegh"与字符串"hgebcijedg"具有相同的hashCode()
返回值-801038016,并且它们具有reverse的关系。这个例子说明了用jdk中默认的hashCode方法判断字
符串相等或者字符串回文,都存在反例。

2.16 indexOf()

indexOf()总结来说,就是返回各种条件下的第一次出现的索引。indexOf()包含很多方法,下面来一一学习。

2.16.1 indexOf(int ch, int fromIndex)

indexOf的基础方法,很多方法都是他的特例。API中解释返回指定字符第一次出现的字符串内的索引,以指定的索引开始搜索。
源码:

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

indexOfSupplementary(int ch, int fromIndex)源码:用于特殊字符的处理

    private int indexOfSupplementary(int ch, int fromIndex) {
        if (Character.isValidCodePoint(ch)) {
            final char[] value = this.value;
            final char hi = Character.highSurrogate(ch);
            final char lo = Character.lowSurrogate(ch);
            final int max = value.length - 1;
            for (int i = fromIndex; i < max; i++) {
                if (value[i] == hi && value[i + 1] == lo) {
                    return i;
                }
            }
        }
        return -1;
    }

从源码中可以看出,这个方法可以指定在第几索引处开始进行检索判断,返回ch出现的第一个位置。ch就是ASCII码,如果ch的值属于补码,则在indexOfSupplementary中做判断。

2.16.2 indexOf(int ch)

2.16.1方法中,规定了默认从0开始
源码:

    public int indexOf(int ch) {
        return indexOf(ch, 0);
    }
2.16.3 lastIndexOf(int ch, int fromIndex)

返回指定字符的最后一次出现的字符串中的索引。 对于从0到0xFFFF(含)范围内的ch的值, ch的索引(以Unicode代码为单位)是最大的值k ,使得:
this.charAt(k) == ch
是真的。 对于ch其他值,它是最大值k ,使得:
this.codePointAt(k) == ch
是真的。 在任何一种情况下,如果此字符串中没有此类字符,则返回-1 。 String从最后一个字符开始String搜索。
源码:

    public int lastIndexOf(int ch, int fromIndex) {
        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;
            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);
        }
    }

lastIndexOfSupplementary(ch, fromIndex)源码:

    private int lastIndexOfSupplementary(int ch, int fromIndex) {
        if (Character.isValidCodePoint(ch)) {
            final char[] value = this.value;
            char hi = Character.highSurrogate(ch);
            char lo = Character.lowSurrogate(ch);
            int i = Math.min(fromIndex, value.length - 2);
            for (; i >= 0; i--) {
                if (value[i] == hi && value[i + 1] == lo) {
                    return i;
                }
            }
        }
        return -1;
    }
2.16.4 lastIndexOf(int ch)

2.16.3的缺省fromIndex 默认为字符串最大长度-1
源码:

    public int lastIndexOf(int ch) {
        return lastIndexOf(ch, value.length - 1);
    }
2.16.5 indexOf() 对于字符串的处理

这部分主要有一个方法,indexOf(char[] source, int sourceOffset, int sourceCount, char[] target, int targetOffset, int targetCount, int fromIndex),剩下的三个方法都是这个方法的特殊情况的调用。
下面开始分析源码:

    static int indexOf(char[] source, int sourceOffset, int sourceCount,
            char[] target, int targetOffset, int targetCount,
            int fromIndex) {
        if (fromIndex >= sourceCount) {
            return (targetCount == 0 ? sourceCount : -1);
        }
        if (fromIndex < 0) {
            fromIndex = 0;
        }
        if (targetCount == 0) {
            return fromIndex;
        }

        char first = target[targetOffset];
        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;
    }

首先来解释一下每个参数的含义,
source–源字符串数组
sourceOffset–源字符串数组的开始位置
sourceCount–源字符串数组获取数量
target–目标字符串数组
targetOffset–目标字符串数组的开始位置
targetCount–目标字符串数组获取数量
fromIndex–源字符串数组在第几索引开始

这段源码的逻辑就是首先获取目标字符串数组的第一个字符

未完待续、、、

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值