「OpenJdk-11 源码-系列」 : String

628 阅读5分钟

类的定义

在日常开发中,String可以说是最常用的类之一了,但也是最容易被忽视的类。先来看看String的定义

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

	private final char[] value; // jdk 8
    private final byte[] value; // jdk 9 使用 byte 减少空间的浪费,

    private final byte coder;
    private int hash; // Default to 0
    private static final long serialVersionUID = -6849794470754667710L;
}

首先,Stringfinal所修饰,同时实现了SerializableComparableCharSequence接口。我们一个个来看:

为撒要用 final 修饰

根据源码,我们可以看到String的值在内部是用一个byte数组value维护的,所以value是一个引用类型,对于引用类型的变量,我们往往都会十分小心,因为鬼知道在什么时候,我们就会掉进一个大坑... 更何况String是一个非常常用的类。所以一个非常重要的原因就是安全性,使得String类需要被final修饰,即不可变。所以对String类加上了final修饰符,防止String被继承,从而重写value。刚才说到value是一个引用类型,所以在value上也加上了final修饰,并且在整个类中,没有对value进行update的操作。所有的update操作都是会新建一个新的string

为撒实现 Serializable 接口

详情可见🔎「OpenJdk-11 源码-系列」Serializable

Comparable & Comparator

先来看看Comparable接口

public interface Comparable<T> {
    public int compareTo(T o);
}

Comparable接口是一个排序接口,若一个类实现了该接口并重写了compareTo方法,就意味着这个类支持了排序。当存在一个实现了Comparable接口的类的集合或数组,那么该集合或数组就可以通过Collections.sort / Arrays.sort进行排序。

  • 当返回值为正数,那么就意味着 a > b (a.compareTo(b))
  • 当返回值为零,那么a = b
  • 当返回值为负数,那么a < b

再来看看Comparator 接口

public interface Comparator<T> {
    //最主要的俩方法
    int compare(T o1, T o2);
    boolean equals(Object obj);
}

刚才我们说到如果一个类实现了Comparable接口后,那么该类就有了排序的功能。但有的时候我们不想修改这个类,减少对类的侵入。那么我们就可以自定义一个类来实现Comparator接口。

总结一下,实现Comparable接口可以做到内部比较(即相同类之间的比较);而实现了Comparator接口的类,可以使用该类对任意两个类型的对象进行比较,且无侵入性。

属性

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    private final byte[] value;
    private final byte coder;
    private int hash; // Default to 0

}
  • value

    value存储的是 String的内容,即当使用String str = "abc";的时候,"abc"是存储在一个byte类型的数组中的。这在后续的构造函数中会讲解到。

  • coder

    在JDK9中,String维护了这样一个新的属性coder,它是一个编码格式的标识,表示LATIN1或者UTF-16,String生成的时候会自动初始化这个值,如果字符串中都是能用LATIN1就能表示的就是0,否则就是UTF-16。

    那么为什么要加这个coder属性呢?先说结论,可以对字符串的空间进行压缩。先看源码

    //jdk8
        public int length() {
            return value.length;
        }
    //jdk9
        static final boolean COMPACT_STRINGS;
        static {
            COMPACT_STRINGS = true; //默认开启压缩
        }    
        byte coder() {
            return COMPACT_STRINGS ? coder : UTF16;
        }
        public int length() {
            return value.length >> coder(); //如果coder() = 1则会右移一位
        }
    

    所以,如果当我们调用length方法的时候,如果valueLATIN1编码的话那么就会右移一位,减少一个字节数。

  • hash

    hashString在实例化的时候对hashcode的一个缓存。由于在开发中String会经常拿来比较,比如HashMap中如果keyString类型,每次比较如果都重新计算hashcode的话就会很费时。

构造方法

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

空参构造方法,会创建一个空的字符串序列。一般不会这么创建。

    //常见的几种构造方法
	public String(byte[] bytes) {
        this(bytes, 0, bytes.length);
    }
	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[], String charsetName)
            throws UnsupportedEncodingException {
        this(bytes, 0, bytes.length, charsetName);
    }
    
    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;
    }
	
	public String(byte bytes[], int offset, int length, Charset charset) {
        if (charset == null)
            throw new NullPointerException("charset");
        checkBoundsOffCount(offset, length, bytes.length);
        StringCoding.Result ret =
            StringCoding.decode(charset, bytes, offset, length);
        this.value = ret.value;
        this.coder = ret.coder;
    }

使用字节数组来创建可以分为指定编码或默认编码(ISO-8859-1)进行编码操作。

常用方法

比较简单,可直接查看API

其它

String 对 + 号的重载

String str = "this is";
String str1 = str + "str"

在底层,Java 对 String 的+的支持使用的是 StringBuilder 以及它的appendtoString方法。即:

String str = "str";
String str1 = (new StringBuilder(String.valueOf(str))).append("str").toString();

intern

public native String intern();

可以看到 intern 是被 native修饰,这说明该方法是由底层的C/C++进行实现的。它的作用是 如果常量池中存在当前字符串, 就会直接返回当前字符串。如果常量池中没有此字符串, 会将此字符串放入常量池中后, 再返回。通过双引号声明的字符串会放入到常量池中

String str = "123";
String str1 = new String("123");
String str2 = str1.intern();
s
System.out.println(str == str1); //false
System.out.println(str == str2); // true