Java源码详解四:String源码分析--openjdk java 11源码

文章目录

    • 注释
    • 类的继承
    • 数据的存储
    • 构造函数
    • charAt函数
    • equals函数
    • hashCode函数
    • indexOf函数
    • intern函数


本系列是Java详解,专栏地址:Java源码分析


String 官方文档:String (Java Platform SE 8 )
String .java源码共3348行,下载地址见我的文章:Java源码下载和阅读(JDK1.8/Java 11)

文件地址:openjdk-jdk11u-jdk-11.0.6-3/src/java.base/share/classes/java/lang


想看String源码的主要原因是String是经常被问到的一个知识点,里面有很多实现有借鉴意义。

注释

看源码的第一步是看注释。
1.String是常量,创建后不可更改。String buffer支持可变的String
2.如果给构造函数传递null,那么会抛出异常NullPointerException
3.String是UTF-16格式的字符串。
4.index,索引指的是char的code unit
5.
6.

类的继承

实现了接口:SerializableCharSequence

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

数据的存储

使用byte数组存储数据,final关键字表示是不可变的,String在创建后就不可修改。

import jdk.internal.vm.annotation.Stable;
@Stable
private final byte[] value;

根据注释:如果String示例是常量,那么会发生常量折叠constant folding
注意,这里有个注解@Stable
如果存在注解@StableHotSpot虚拟机可以做优化,如constant folding。处理final fields就像处理static final fields
该注解仅在boot loader加载类的时候起作用,其他时候不起作用。


因为使用byte数组来存储数据,所以需要指定字符串的编码,支持UTF-16LATIN1

    /**
     * The identifier of the encoding used to encode the bytes in
     * {@code value}. The supported values in this implementation are
     * LATIN1
     * UTF16
     */
    private final byte coder;
    @Native static final byte LATIN1 = 0;
    @Native static final byte UTF16  = 1;

构造函数

空的构造函数还是挺新奇的。支持从Stringchar []byte []int []StringBufferStringBuilder进行构造。
其中int []Unicode code point
byte []可以指定编码,包括UTF_8ISO_8859_1US_ASCII.

    public String() {
        this.value = "".value;
        this.coder = "".coder;
    }
    @HotSpotIntrinsicCandidate
    public String(String original) {
        this.value = original.value;
        this.coder = original.coder;
        this.hash = original.hash;
    }
    public String(char value[]) {
        this(value, 0, value.length, null);
    }
    public String(char value[], int offset, int count) {
        this(value, offset, count, rangeCheck(value, offset, count));
    }
    public String(byte bytes[], int offset, int length, String charsetName)
            throws UnsupportedEncodingException {
        if (charsetName == null)
            throw new NullPointerException("charsetName");
        checkBoundsOffCount(offset, length, bytes.length);
        StringCoding.Result ret =
            StringCoding.decode(charsetName, bytes, offset, length);
        this.value = ret.value;
        this.coder = ret.coder;
    }
    .....................

charAt函数

可以看出不同的编码格式调用的是不同的函数。

    public char charAt(int index) {
        if (isLatin1()) {
            return StringLatin1.charAt(value, index);
        } else {
            return StringUTF16.charAt(value, index);
        }
    }
    public static char StringLatin1::charAt(byte[] value, int index) {
        if (index < 0 || index >= value.length) {
            throw new StringIndexOutOfBoundsException(index);
        }
        return (char)(value[index] & 0xff);
    }
    public static char StringUTF16::charAt(byte[] value, int index) {
        checkIndex(index, value);
        return getChar(value, index);
    }
    @HotSpotIntrinsicCandidate
    // intrinsic performs no bounds checks
    static char StringUTF16::getChar(byte[] val, int index) {
        assert index >= 0 && index < length(val) : "Trusted caller missed bounds check";
        index <<= 1;
        return (char)(((val[index++] & 0xff) << HI_BYTE_SHIFT) |
                      ((val[index]   & 0xff) << LO_BYTE_SHIFT));
    }

然后我们可以看到一个非常有趣的代码:

return (char)(value[index] & 0xff);

这里有几个知识点:
1.常见的ASCII是从0到126,包括大小写字母和常用符号。但一个byte是一个字节,有8位,最高位为0空着有些浪费,因此有了Extended ASCII
2.Java对字符采用的编码是LATIN1,也称ISO 8859-1ISO/IEC 8859-1:1998Latin-1。采用这个编码的原因是ISO-8859-1对应于ISO/IEC 10646Unicode的前256个码位。
3.因此对于最高位为1的byte来说,在转换为char的过程中最高位会置1(补码),因此需要让最高位永远置0,所以要与上0xff


下面用实例说明,Extended ASCII的165,也就是1010 0101是人民币货币符号¥。

System.out.println((char) ((byte) 165 & 0xff));
System.out.println((char) (byte) 165);

输出结果如下:

¥
ᆬ

equals函数

String实现了equals,具体做法也就是从头比到尾。

    public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String aString = (String)anObject;
            if (coder() == aString.coder()) {
                return isLatin1() ? StringLatin1.equals(value, aString.value)
                                  : StringUTF16.equals(value, aString.value);
            }
        }
        return false;
    }
    @HotSpotIntrinsicCandidate
    public static boolean StringLatin1::equals(byte[] value, byte[] other) {
        if (value.length == other.length) {
            for (int i = 0; i < value.length; i++) {
                if (value[i] != other[i]) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }

hashCode函数

    public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            hash = h = isLatin1() ? StringLatin1.hashCode(value)
                                  : StringUTF16.hashCode(value);
        }
        return h;
    }
    public static int StringLatin1::hashCode(byte[] value) {
        int h = 0;
        for (byte v : value) {
            h = 31 * h + (v & 0xff);
        }
        return h;
    }

& 0xff的原因之前提过了

indexOf函数

    public int indexOf(String str) {
        if (coder() == str.coder()) {
            return isLatin1() ? StringLatin1.indexOf(value, str.value)
                              : StringUTF16.indexOf(value, str.value);
        }
        if (coder() == LATIN1) {  // str.coder == UTF16
            return -1;
        }
        return StringUTF16.indexOfLatin1(value, str.value);
    }
    @HotSpotIntrinsicCandidate
    public static int StringLatin1::indexOf(byte[] value, int valueCount, byte[] str, int strCount, int fromIndex) {
        byte first = str[0];
        int max = (valueCount - strCount);
        for (int i = fromIndex; i <= max; i++) {
            // Look for first character.
            if (value[i] != first) {
                while (++i <= max && value[i] != first);
            }
            // Found first character, now look at the rest of value
            if (i <= max) {
                int j = i + 1;
                int end = j + strCount - 1;
                for (int k = 1; j < end && value[j] == str[k]; j++, k++);
                if (j == end) {
                    // Found whole string.
                    return i;
                }
            }
        }
        return -1;
    }

发现并没有做优化。

intern函数

intern函数经常会在面试中出现,是native函数。

   public native String intern();

intern函数的意义:如果常量池中存在当前字符串, 就会直接返回当前字符串. 如果常量池中没有此字符串, 会将此字符串放入常量池中后, 再返回。

根据注释:s.intern() == t.intern()的结果为true当且仅当s.equals(t)的结果为true

有几个注意事项:
1.所有的字符串常量的字符串都在字符串常量池中。
2.intern函数的意义是如果你new String,那么对象会被放在堆中,并且是新的对象。如果你知道会相同的字符串会用到多次,那么可以使用intern函数,使其进入字符串常量池,减少空间的开销。
3.字符串常量池在java 7后就存在堆中。在JDK 7中,不再将永久字符串分配给Java堆的永久代,而是分配给Java堆的主要部分(称为年轻代和旧代) 。此更改将导致更多数据驻留在主Java堆中,而永久生成中的数据更少,因此可能需要调整堆大小。
4.字符串如何加入字符串常量池,在Java 7 以后,new的对象加入的都是引用。

    public static void main(String[] args) {
        String str2 = new String("str") + new String("01");
        str2.intern();
        String str1 = "str01";
        System.out.println(str2 == str1);  //true

        String s4 = new String("1");
        s4.intern();
        String s5 = "1";
        System.out.println(s4 == s5);   //false

        String s = new StringBuilder("aa").append("bb").toString();
        String s2 = new StringBuilder("cc").toString();
        System.out.println(s.intern() == s);    //true
        System.out.println(s2.intern() == s2);  //false
    }

参考:

  • What is Java String interning? - Stack Overflow
  • Java String intern function result problem - Stack Overflow
  • 深入理解String.intern - 后端 - 掘金
  • 深入解析String#intern - 美团技术博客


其他函数并没有值得说的部分。

你可能感兴趣的:(java源码分析,java)