源码阅读-java基础-java.lang.String

1、概述

String这个类可以说是我们使用最为频繁的类之一了,它在java的数据类型里面有很重要的地位,今天我们来看看它的源码,以及分析一下它的不可变性。

2、概念解释

2.1、底层结构

以下是String的部分源码。我们可以看到它是一个用final修饰的常量类,不能被任何类所继承,而且一旦一个String对象被创建, 包含在这个对象中的字符序列是不可改变的, 包括该类后续的所有方法都是不能修改该对象的,直至该对象被销毁,这是我们需要特别注意的(该类的一些方法看似改变了字符串,其实内部都是创建一个新的字符串)。接着实现了 Serializable接口,这是一个序列化标志接口,还实现了 Comparable 接口,用于比较两个字符串的大小(按顺序比较单个字符的ASCII码),最后实现了 CharSequence 接口,表示是一个有序字符的集合。

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
    
    private final char value[];

    private int hash; 

    private static final long serialVersionUID = -6849794470754667710L;
    
    private static final ObjectStreamField[] serialPersistentFields =
        new ObjectStreamField[0];

    ......
}

2.2、CharSequence

这个接口参考源码阅读-java基础-java.lang.CharSequence

2.3、几个数组复制方法

阅读String的源码,你会看到System.arraycopy、Arrays.copyOf、Arrays.copyOfRange这几个方法经常出现,这三个方法操作value(即 存储String的字符数组),所以我们有必要看看这三个方法是干嘛的。这三个方法都是复制数组用的,只是前者是基础方法;而后两者是稍微高级的方法,底层调用了System.arraycopy方法。

System.arraycopy方法是一个静态的,本地方法,所以我们一般是看不到它的实现过程的。虽然它的参数是Object,但是java的数组没有一个比较抽象的公共的“父类”或者“接口”,所以这里的Object src,Object dest可以理解为数组,但是呢,我是没想通,既然这个方法是用于数组的,为啥不写成Object[] src,Object[] dest呢,这样意思不是更加明显吗?有知道的同学可以在评论区留言一下。

/**
  * src:源数组
  * srcPos:源数组的起始索引
  * dest:目标数组
  * destPos:目标数组的起始索引
  * length:复制的长度
  */
public static native void arraycopy(Object src,int srcPos,Object dest,int destPos,int length);

2.4、String类型真的不可变吗

参考java中String的不可变性.

3、常用方法

3.1、int length():获取长度

返回String的长度,它是java.lang.CharSequence接口定义的方法,只不过String对其进行了重写,源码里面是没有@Override注解的,我为了看得清楚,增加了这个注解。我们看到它返回的实际上就是字符数组的长度。

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

3.2、char charAt(int index):返回数组某个索引处的的字符

如果索引超限,则抛出索引越界异常,底层操作的还是字符数组。它也是java.lang.CharSequence的方法。

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

3.3、boolean isEmpty():字符串是否为空

我们看到操作的还是value数组。

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

3.4、boolean equals(Object anObject):比较内容是否一样

我们看到,首先比较了指针是否指向同一个地址,然后比较了类型,最后比较了内容(在比较内容的时候,为了减少比较的次数,使用了i++,n--的思想)。整体的思想值得借鉴。另外,这里即便参数为null,程序也能正常运行。

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

3.5、int compareTo(String anotherString)

这是实现Comparable接口必须要实现的方法。

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

3.6、int hashCode():获取哈希值

这里看到String的hashCode()实现还是很简单的,真正的代码就三行,但是有个奇怪的数字——31,为啥是31呢?

  • 31是一个不大不小的质数,是作为 hashCode 乘子的优选质数之一。选择数字31是因为它是一个奇质数,如果选择一个偶数会在乘法运算中产生溢出,导致数值信息丢失,因为乘二相当于移位运算。选择质数的优势并不是特别的明显,但这是一个传统。同时,数字31有一个很好的特性,即乘法运算可以被移位和减法运算取代,来获取更好的性能。
  • 31可以被 JVM 优化,31 * i = (i << 5) - i

    具体的可以看看这篇博文:为什么 String hashCode 方法选择数字31作为乘子,我感觉这位大佬的这篇博文的质量非常高,是带着学术的精神去研究这个问题,值得膜拜!
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;
}

3.7、String substring:字符串截取

字符串的截取。其中方法的参数是“起始索引”,截取的长度是value.length - beginIndex,如果起始索引超限,则抛出索引越界异常!但是呢,值得注意的是,String的几乎所有方法,都不会操作原来的value数组,而是复制一份出来,再进行操作。

public String substring(int beginIndex) {
    if (beginIndex < 0) {
        throw new StringIndexOutOfBoundsException(beginIndex);
    }
    int subLen = value.length - beginIndex;
    if (subLen < 0) {
        throw new StringIndexOutOfBoundsException(subLen);
    }
    return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}

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

3.8、String 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);
}

3.9、String 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;
}

3.10、boolean contains(CharSequense s):包含

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

3.11、String trim():去掉字符串前后的空格

public String trim() {
    int len = value.length;
    int st = 0;
    char[] val = value;    /* avoid getfield opcode */

    while ((st < len) && (val[st] <= ' ')) {
        st++;
    }
    while ((st < len) && (val[len - 1] <= ' ')) {
        len--;
    }
    return ((st > 0) || (len < value.length)) ? substring(st, len) : this;
}

3.12、String toString():经常用,就是返回字符串本身

public String toString() {
    return this;
}

3.13、boolean 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;
}

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

3.14、boolean endsWith():是否以某个字符串结尾

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

4、构造方法

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

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

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

public String(byte bytes[]) {
    this(bytes, 0, bytes.length);
}
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());
}
String(char[] value, boolean share) {
    // assert share : "unshared not supported";
    this.value = value;
}

5、本地方法

5.1、String intern()

6、其他方法

6.1、indexOf系列

//todo

6.2、valueOf系列

//todo

7、未解决问题

  • System.arraycopy(Object src,int srcPos,Object dest,int destPos,int length),既然是Jdk提供的一个只用于数组复制的方法,为啥表示数组的参数类型是Object,表示成Object[]不是更一目了然吗?

8、参考文档

posted @ 2021-01-19 15:16  Erneste  阅读(89)  评论(0编辑  收藏  举报