String notion、String 语雀 :因为对底层发现char[] ——> byte[] ,本文是找的别人的。
通过JDK8与JDK11的对比,很容易发现String 类的源码已经由 char[] 优化为了 byte[] 来存储字符串内容。
其实是从JDK9开始变得,因为JDK8与JDK11都是LTS版本,我就用JDK11列举源码。
为什么要这样做呢?
一、为什么要优化 String 节省内存空间
使用 jmap -histo:live pid | head -n 10 命令就可以查看到堆内对象示例的统计信息、查看 ClassLoader 的信息以及 finalizer 队列。
ps:我试了,Windows上缺少NativeIO需要手动创建。
以正在运行着的项目实例(基于 Java 8)来说
其中 String 对象有 17638 个,占用了 423312 个字节的内存,排在第三位。
由于 Java 8 的 String 内部实现仍然是 char[],所以可以看到内存占用排在第 1 位的就是 char 数组。
char[] 对象有 17673 个,占用了 1621352 个字节的内存,排在第一位。
所以说优化 String 节省内存空间是非常有必要的
二、byte[] 为什么就能节省内存空间呢?
char 类型的数据在 JVM 中是占用两个字节的,并且使用的是 UTF-8 编码,其值范围在 ‘\u0000’(0)和 ‘\uffff’(65,535)(包含)之间。
使用 char[] 来表示 String 就导致了即使 String 中的字符只用一个字节就能表示,也得占用两个字节。
实际开发中,单字节的字符使用频率仍然要高于双字节的。
仅仅将 char[] 优化为 byte[] 是不够的,还要配合 Latin-1 的编码方式
该编码方式是用单个字节来表示字符的,这样就比 UTF-8 编码节省了更多的空间。
例如:String name = “jack”;
使用 Latin-1 编码,占用 4 个字节;
但对于:
String name = “小二”;
只能使用 UTF16 来编码。
所以JDK8之后 String 源码里,为了区别编码方式,追加了一个 coder 字段来区分。
Java 会根据字符串的内容自动设置为相应的编码,要么 Latin-1 要么 UTF16。
也就是说,从 char[] 到 byte[],中文是两个字节,纯英文是一个字节,
在此之前,中文是两个字节,英文也是两个字节。
三、为什么用UTF-16而不用UTF-8呢?
在 UTF-8 中,0-127 号的字符用 1 个字节来表示,使用和 ASCII 相同的编码。
只有 128 号及以上的字符才用 2 个、3 个或者 4 个字节来表示。
具体的表现形式为:
- 0xxxxxxx:一个字节;
- 110xxxxx 10xxxxxx:两个字节编码形式(开始两个 1);
- 1110xxxx 10xxxxxx 10xxxxxx:三字节编码形式(开始三个 1);
- 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx:四字节编码形式(开始四个 1)。
也就是说,UTF-8 是变长的,那对于 String 这种有随机访问方法的类来说,就很不方便。所谓的随机访问,就是charAt、subString这种方法,随便指定一个数字,String要能给出结果。如果字符串中的每个字符占用的内存是不定长的,那么进行随机访问的时候,就需要从头开始数每个字符的长度,才能找到想要的字符。
UTF-16 使用 2 个或者 4 个字节来存储字符。
- 对于 Unicode 编号范围在 0 ~ FFFF 之间的字符,UTF-16 使用两个字节存储。
- 对于 Unicode 编号范围在 10000 ~ 10FFFF 之间的字符,UTF-16 使用四个字节存储,具体来说就是:将字符编号的所有比特位分成两部分,较高的一些比特位用一个值介于 D800~DBFF 之间的双字节存储,较低的一些比特位(剩下的比特位)用一个值介于 DC00~DFFF 之间的双字节存储。
但是在 Java 中,一个字符(char)就是 2 个字节,占 4 个字节的字符,在 Java 里也是用两个 char 来存储的,而String的各种操作,都是以Java的字符(char)为单位的,charAt是取得第几个char,subString取的也是第几个到第几个char组成的子串,甚至length返回的都是char的个数。
所以UTF-16在Java的世界里,就可以视为一个定长的编码。
参考链接:https://www.zhihu.com/question/447224628