JDK17新特性之--新的Compact Strings(JEP 254)

前言

JDK9将String底层的数据结构从private final char value[];改成了private final byte[] value;
JEP 254: Compact Strings(紧凑字符串),这要修改的目的就是为了节省空间1。我们先看一下JDK9和JDK8中String源码的变化。
JDK9中String源码:

public final class String 
implements java.io.Serializable, Comparable<String>, CharSequence,  
Constable, ConstantDesc {  
    @Stable  
    private final byte[] value;  

    * LATIN1  
    * UTF16  
    private final byte coder;
    
    @Native static final byte LATIN1 = 0;  
    @Native static final byte UTF16 = 1;

JDK8中String源码:

public final class String  
    implements java.io.Serializable, Comparable<String>, CharSequence {  
    private final char value[];  
    private int hash; 
}

对比源码可以看出,在JDK9中使用了byte[]代替了char[]同时还增加了一个 coder标志位表示使用的是LATIN1还是UTF16编码。

节省了多大空间

既然这个升级是为了节省内存空间,那么我们就先来测试一下看看节省了多少空间。我们创建一个简单的Demo,创建一个List然后向里面添加1-20000000个String,再对比一下JDK9和JDK8占用内存空间大小。

public static void main(String[] args) throws InterruptedException {  
        List<String> name= IntStream.range(0,20000000).mapToObj(String::valueOf).collect(Collectors.toList());;  
        System.out.printf("add complete "+name.size());  
        Thread.sleep(100000000L);
}

JDK8内存使用情况

JDK8内存使用情况

JDK9内存使用情况

JDK9内存使用情况

从图中可以看出同样是1-20000000字符串JDK8中char[]占用了742MB,JDK9中byte[]占用了559MB,节省了32.7%内存空间。

没有Jprofile 也可以使用jps+jmap查看内存占用情况,以下为Windows操作

  1. .\jps.exe 获取出PID
PS C:\Program Files\Java\jdk1.8.0_191\bin> .\jps.exe
9328 RemoteMavenServer36
9744 Launcher
10548
2196 Jps
7224 RemoteMavenServer36
8120 Main
  1. 通过Jmap查看内存中对象统计
JDK8:
PS C:\Program Files\Java\jdk1.8.0_191\bin> .\jmap.exe -histo:live 8120

 num     #instances         #bytes  class name
----------------------------------------------
   1:      20004394      640375664  [C
   2:      20004267      480102408  java.lang.String
   3:           639       83113224  [Ljava.lang.Object;
   4:           685          77712  java.lang.Class
   5:           791          31640  java.util.TreeMap$Entry
   6:            26          25752  [B
   7:           657          21024  java.util.HashMap$Node
   8:           315          13488  [Ljava.lang.String;
   9:           126           8272  [I
  10:           123           7872  java.net.URL
  
JDK9:

PS C:\Program Files\Java\jdk1.8.0_191\bin> .\jmap.exe -histo:live 4116
 num     #instances         #bytes  class name (module)
-------------------------------------------------------
   1:      20003758      480237256  [B ([email protected])
   2:      20003674      480088176  java.lang.String ([email protected])
   3:          1042       83141776  [Ljava.lang.Object; ([email protected])
   4:           137         142376  [C ([email protected])
   5:           818         100864  java.lang.Class ([email protected])
   6:          1242          39744  java.util.HashMap$Node ([email protected])
   7:          1084          34688  java.util.concurrent.ConcurrentHashMap$Node ([email protected])
   8:           330          30240  [Ljava.util.HashMap$Node; ([email protected])
   9:           336          16128  java.util.HashMap ([email protected])
  10:            22          15392  [Ljava.util.concurrent.ConcurrentHashMap$Node; ([email protected])

从Jmap输出内存对象统计来看 JDK8中[C就是 char[] ,实例数有2000万多点,占用内存610MB,JDK9 [B 就是byte[],实例数也是2000万多点,占用内存457MB,节省内存33.4%。值的注意的是如果不想使用字符串压缩可以使用 -XX:-CompactStrings JVM参数来禁用此功能

是怎么节省空间的

JAVA中一个char到底占用多大空间?在JVM规范中char是使用一个16-bit无符号整型来表示一个Unicode code2,所以一个char可以理解占用两个字节空间,但是我们可以想一下常用的ascii码其实只要一个字节就能表示了。JDK9里对Latin-1(又称ISO-8859-1)字符使用一个byte存储,只占用了一个字节,如果不是Latin-1字符就使用UTF-16存储,占用两个字节,为此JDK9中增加了coder标志位,这样对于使用最频繁的abcd… 1234…等字母、数字来说就节省了50%空间,然而对于中文或者不在Latin-1字符来说还是和以前一样,并不能节省内存空间。那么Latin-1到底有哪些字符呢?Latin-1(也称为ISO 8859-1),是一种字符编码标准,用于表示拉丁字母语系的字符集。它包含了256个字符3

  • 26个基本拉丁字母,包括大小写字母 a-z,A-Z
  • 数字0-9
  • 逗号、句号、问号等常见标点符号
  • 特殊符号,如@、#、$、%等
  • 一些重音符号和其他符号,如é、ç、ñ等
    具体可以看以下图片:
    在这里插入图片描述

总结

1. 更少的内存占用:

采用 Compact Strings 后,只包含 ASCII 字符或者Latin-1的字符串在内存中的占用空间会减少一半,从而可以减少内存的使用量,提高程序的性能。

2.String内置方法都重写了

String内置的方法像equals、compareTo等都重写了。

    public int compareTo(String anotherString) {
        int len1 = value.length;
        int len2 = anotherString.value.length;
        int lim = Math.min(len1, len2);
        char v1[] = value;
        char v2[] = anotherString.value;

        int k = 0;
        while (k < lim) {
            char c1 = v1[k];
            char c2 = v2[k];
            if (c1 != c2) {
                return c1 - c2;
            }
            k++;
        }
        return len1 - len2;
    }

   public int compareTo(String anotherString) {
        byte v1[] = value;
        byte v2[] = anotherString.value;
        byte coder = coder();
        if (coder == anotherString.coder()) {
            return coder == LATIN1 ? StringLatin1.compareTo(v1, v2)
                                   : StringUTF16.compareTo(v1, v2);
        }
        return coder == LATIN1 ? StringLatin1.compareToUTF16(v1, v2)
                               : StringUTF16.compareToLatin1(v1, v2);
     }

3.更快的字符串操作:

由于 Compact Strings 的内部表示方式采用了byte[]存储数据,可以更快地执行字符串操作,例如字符串比较、拼接等。

String生成测试Demo:

        Long startTime=System.currentTimeMillis();
        List<String> name= IntStream.range(0,20000000).mapToObj(String::valueOf).collect(Collectors.toList());;
        System.out.println("add  "+name.size()+" complete ,cast "+(System.currentTimeMillis()-startTime)+"ms"); 
       //JDK9:
       add  20000000 complete ,cast 1313ms
       //JDK8
       add  20000000 complete ,cast 13777ms

从生成2000万个String速度来看,JDK9只花了1.3S,而JDK8花了13S,确实快了不少。

String拼接Demo:

        Long startTime = System.currentTimeMillis();
        List<String> name = IntStream.range(0, 20000000).mapToObj(x -> String.valueOf(x) + x).collect(Collectors.toList());
        System.out.println("add  " + name.size() + " complete ,cast " + (System.currentTimeMillis() - startTime) + "ms");
  //JDK9
  add  20000000 complete ,cast 1832ms
  //JDK8
  add  20000000 complete ,cast 25147ms
        

从拼接2000万个String速度来看,JDK9只花了1.8S,而JDK8花了25S,确实也快了不少。

4.更高的兼容性:

Compact Strings 不会影响现有的 Java 代码,因此可以与现有的 Java 应用程序兼容,无需修改代码,这点是肯定的,如果要修改Java String操作的代码全球有多少人要抓狂,最后肯定都选择不升级了。


  1. JEP 254: Compact Strings (openjdk.org) ↩︎

  2. (11 封私信) Java中关于Char存储中文到底是2个字节还是3个还是4个? - 知乎 (zhihu.com) ↩︎

  3. ISO/IEC 8859-1 - Wikipedia ↩︎

猜你喜欢

转载自blog.csdn.net/whzhaochao/article/details/130536403