在 Java 语言中,Sun 公司的工程师们对 String 对象做了大量的优化,来节约内存空间,提升 String 对象在系统中的性能。一起来看看优化过程,如下图所示:

img

工程师将 char[] 字段改为了 byte[] 字段,又维护了一个新的属性 coder,它是一个编码格式的标识。

工程师为什么这样修改呢?

我们知道一个 char 字符占 16 位,2 个字节。这个情况下,存储单字节编码内的字符(占一个字节的字符)就显得非常浪费。JDK1.9 的 String 类为了节约内存空间,于是使用了占 8 位,1 个字节的 byte 数组来存放字符串。

因此在Java9中,引入了一个叫做紧凑字符串(Compact String)的新特性,在String内部使用byte[]代替char[]。如果字符串的内容都是ISO-8859-1/Latin-1字符(1个字符1byte),则使用ISO-8859-1/Latin-1编码存储字符串,否则使用UTF-16编码存储数组(一个字符2byte或者4byte)。但是为了兼容,String类的方法向外表现的行为是与之前的版本一致的,比如toCharArray()和charAt()方法的行为依然和之前一致。

为了支持这一特性,Java引入了StringLatin1和StringUTF16两个新的辅助类用来提供静态方法供String类使用,举个例子,为了保证charAt()行为与之前版本的一致,Java9中的代码如下: ———————————————— 版权声明:本文为CSDN博主「I am zzxn」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qq_39438086/article/details/105424910

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class String {
...
    public char charAt(int index) {
        if (isLatin1()) { // 如果是Latin1编码
            return StringLatin1.charAt(value, index);
        } else { // 否则,是UTF16编码
            return StringUTF16.charAt(value, index);
        }
    }
...
}

final class StringLatin1 {
...
    public static char charAt(byte[] value, int index) {
        if (index < 0 || index >= value.length) {
            throw new StringIndexOutOfBoundsException(index);
        }
        return (char)(value[index] & 0xff);
    }
...
}

final class StringUTF16 {
...
	public static char charAt(byte[] value, int index) {
        checkIndex(index, value);
        return getChar(value, index); // 在HotSpot虚拟机中,这个方法默认使用intrinsic实现
    }
...
}

java8 不再共享char[] 的原因

  1. “String.substring 方法也不再共享 char[],从而解决了使用该方法可能导致的内存泄漏问题。”

    • 在Java6中substring方法会调用new string构造函数,此时会复用原来的char数组,而如果我们仅仅是用substring获取一小段字符,而原本string字符串非常大的情况下,substring的对象如果一直被引用,由于substring的里面的char数组仍然指向原字符串,此时string字符串也无法回收,从而导致内存泄露。
    • 试想下,如果有大量这种通过substring获取超大字符串中一小段字符串的操作,会因为内存泄露而导致内存溢出。