如果问你,开发过程中用的最多的类是哪个?你可能回答是HashMap
,一个原因就是HashMap的使用量的确很多,还有就是HashMap的内容在面试中经常被问起。
但是在开发过程中使用最多的类其实并不是HashMap类,而是“默默无闻”的String类。假如现在问你String类是怎么实现的?这个类为什么是不可变类?这个类为什么不能被继承?这些问题你都能回答么。本文就从String源代码出发,来看下String到底是怎么实现的,并详细介绍下String类的API的用法。
String源码结构
首先要说明的是本文的源码是以JDK11为基准,选择JDK11的原因是JDK11是一个LTS版本(长期支持版本),没选择现阶段还在广泛使用的JDK8的原因是想在看源码的过程中学习下JDK的新特性。
还有要说下的就是:大家在看源码时一定要注意JDK的版本,因为不同版本的实现有较大的差异。比如说String的实现在高低版本中就差异比较大。如果你是一个博客主,更加要注明代码的版本了,不然读者可能会很疑惑,为什么和自己之前看的不一样。
好了,下面就言归正传来看下String在JDK11中的实现代码。
public final class String implements Serializable, Comparable<String>, CharSequence {
@Stable
//字节数组,存放String的内容,如果你看的是较低版本的源代码,这个变量可能是char[]类型,这个其实是JDK9开始对String做的一个优化
//具体是做了什么优化我们下面再讲,这边先卖个关子
private final byte[] value;
//也是和String压缩优化有关,指定当前的LATIN1码还是UTF16码
private final byte coder;
//哈希值
private int hash;
//序列化Id
private static final long serialVersionUID = -6849794470754667710L;
//优化压缩开关,默认开启
static final boolean COMPACT_STRINGS = true;
private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];
public static final Comparator<String> CASE_INSENSITIVE_ORDER = new String.CaseInsensitiveComparator();
static final byte LATIN1 = 0;
static final byte UTF16 = 1;
//... 下面部分代码省略
}
从实现的接口看,String类有如下特点:
- String类被final关键字修饰,因此不能被继承。
- String的成员变量value使用final修饰,因此是不可变的,线程安全;
- String类实现了Serializable接口,可以实现序列化。
- String类实现了Comparable,可以比较大小。
- String类实现了CharSequence接口,String本质是个数组,低版本中是char数组,JDK9以后优化成byte数组,从String的成员变量value就可以看出来。
这边说一个看源代码的小技巧:看一个类的源代码时,我们先看下这个类实现了哪些接口,就可以大概知道这个类的主要作用功能是什么了。
JDK9对String的优化
这边首先要讲下JDK 9
中对String的优化,如果你不了解这块优化点的话,看String的代码时会感到非常疑惑。
背景知识
在Java中,一个字节char占用两个字节的内存空间。在低版本的JDK中,String的内部默认维护的是一个char[]数组,也就是说一个字符串中包含一个字符,这个字符串内部就包含一个相应长度的字符数组。这样就会出现下面这种情况:
String s = "ddd";
String s1 = "自由之路";
上面两个字符串内部的情况实际上是:
char[] value = ['d','d','d'];
char[] value1 = ['自','由','之','路'];
对于字符串s,我们发现其中每个字符其实都是可以用一个字节表示的,而现在使用两个字符的char类型来表示,明显就浪费了一倍的内存空间。
而且根据统计,在实际程序运行中&