JDK9为何要将String的底层实现由char[]改成了byte[]

 更新时间:2022年03月25日 09:43:06   作者:公众号_IT老哥  
String 类的源码已经由 char[] 优化为了 byte[] 来存储字符串内容,为什么要这样做呢?本文就详细的介绍一下,感兴趣的可以了解一下

如果你不是 Java8 的钉子户,你应该早就发现了:String 类的源码已经由 char[] 优化为了 byte[] 来存储字符串内容,为什么要这样做呢?

开门见山地说,从 char[] 到 byte[],最主要的目的是为了节省字符串占用的内存。内存占用减少带来的另外一个好处,就是 GC 次数也会减少。

一、为什么要优化 String 节省内存空间

我们使用 jmap -histo:live pid | head -n 10 命令就可以查看到堆内对象示例的统计信息、查看 ClassLoader 的信息以及 finalizer 队列。

以我正在运行着的编程喵喵项目实例(基于 Java 8)来说,结果是这样的。

其中 String 对象有 17638 个,占用了 423312 个字节的内存,排在第三位。

由于 Java 8 的 String 内部实现仍然是 char[],所以我们可以看到内存占用排在第 1 位的就是 char 数组。

char[] 对象有 17673 个,占用了 1621352 个字节的内存,排在第一位。

那也就是说优化 String 节省内存空间是非常有必要的,如果是去优化一个使用频率没有 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 来编码。

针对 JDK 9 的 String 源码里,为了区别编码方式,追加了一个 coder 字段来区分。

/**  
 * The identifier of the encoding used to encode the bytes in  
 * {@code value}. The supported values in this implementation are  
 *  
 * LATIN1  
 * UTF16  
 *  
 * @implNote This field is trusted by the VM, and is a subject to  
 * constant folding if String instance is constant. Overwriting this  
 * field after construction will cause problems.  
 */  
private final byte coder; 

Java 会根据字符串的内容自动设置为相应的编码,要么 Latin-1 要么 UTF16。

也就是说,从 char[] 到 byte[]中文是两个字节,纯英文是一个字节,在此之前呢,中文是两个字节,英文也是两个字节

三、为什么用UTF-16而不用UTF-8呢?

在 UTF-8 中,0-127 号的字符用 1 个字节来表示,使用和 ASCII 相同的编码。只有 128 号及以上的字符才用 2 个、3 个或者 4 个字节来表示。

  • 如果只有一个字节,那么最高的比特位为 0;

  • 如果有多个字节,那么第一个字节从最高位开始,连续有几个比特位的值为 1,就使用几个字节编码,剩下的字节均以 10 开头。

具体的表现形式为:

  • 0xxxxxxx:一个字节;

  • 110xxxxx 10xxxxxx:两个字节编码形式(开始两个 1);- 1110xxxx 10xxxxxx 10xxxxxx:三字节编码形式(开始三个 1);

  • 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx:四字节编码形式(开始四个 1)。

也就是说,UTF-8 是变长的,那对于 String 这种有随机访问方法的类来说,就很不方便。所谓的随机访问,就是charAt、subString这种方法,随便指定一个数字,String要能给出结果。如果字符串中的每个字符占用的内存是不定长的,那么进行随机访问的时候,就需要从头开始数每个字符的长度,才能找到你想要的字符。

那有小伙伴可能会问,UTF-16也是变长的呢?一个字符还可能占用 4 个字节呢?

的确,UTF-16 使用 2 个或者 4 个字节来存储字符。

  • 对于 Unicode 编号范围在 0 ~ FFFF 之间的字符,UTF-16 使用两个字节存储。

  • 对于 Unicode 编号范围在 10000 ~ 10FFFF 之间的字符,UTF-16 使用四个字节存储,具体来说就是:将字符编号的所有比特位分成两部分,较高的一些比特位用一个值介于 D800DBFF 之间的双字节存储,较低的一些比特位(剩下的比特位)用一个值介于 DC00DFFF 之间的双字节存储。

但是在 Java 中,一个字符(char)就是 2 个字节,占 4 个字节的字符,在 Java 里也是用两个 char 来存储的,而String的各种操作,都是以Java的字符(char)为单位的,charAt是取得第几个char,subString取的也是第几个到第几个char组成的子串,甚至length返回的都是char的个数。

所以UTF-16在Java的世界里,就可以视为一个定长的编码。

到此这篇关于JDK9为何要将String的底层实现由char[]改成了byte[]的文章就介绍到这了,更多相关JDK9 char[]改成了byte[]内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SpringBoot @Validated注解实现参数分组校验的方法实例

    SpringBoot @Validated注解实现参数分组校验的方法实例

    这篇文章主要给大家介绍了关于SpringBoot @Validated注解实现参数分组校验的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-09-09
  • Java synchronized关键字性能考量及优化探索

    Java synchronized关键字性能考量及优化探索

    这篇文章主要为大家介绍了Java synchronized关键字性能考量及优化探索示例分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-12-12
  • Java之maven打完jar包之后将jar包放到指定位置汇总

    Java之maven打完jar包之后将jar包放到指定位置汇总

    这篇文章主要介绍了Java之maven打完jar包之后将jar包放到指定位置汇总,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-04-04
  • springboot 整合 SA-Token 使用详解

    springboot 整合 SA-Token 使用详解

    本文详细介绍了SA-Token这款安全框架的使用,并结合实际操作演示了如何集成到springboot项目中,感兴趣的朋友跟随小编一起看看吧
    2024-08-08
  • 一文教你如何更改IDEA已有项目的路径/名称

    一文教你如何更改IDEA已有项目的路径/名称

    由于IDEA项目路径中有中文、空格等特殊符号,影响正常使用,想要修改路径名称,怎么正确修改IDEA项目名称,使其正常运行呢?所以本文小编讲给大家详细的介绍了更改IDEA已有项目的路径/名称解决方案,需要的朋友可以参考下
    2023-11-11
  • Spring框架中一个有用的小组件之Spring Retry组件详解

    Spring框架中一个有用的小组件之Spring Retry组件详解

    Spring Retry 是从 Spring batch 中独立出来的一个功能,主要实现了重试和熔断,对于那些重试后不会改变结果,毫无意义的操作,不建议使用重试,今天通过本文给大家介绍Spring Retry组件详解,感兴趣的朋友一起看看吧
    2021-07-07
  • java把excel内容上传到mysql实例代码

    java把excel内容上传到mysql实例代码

    这篇文章主要介绍了java把excel内容上传到mysql实例代码,具有一定借鉴价值,需要的朋友可以参考下
    2018-01-01
  • Java实现断点下载功能的示例代码

    Java实现断点下载功能的示例代码

    当下载一个很大的文件时,如果下载到一半暂停,如果继续下载呢?断点下载就是解决这个问题的。本文将用Java语言实现断点下载,需要的可以参考一下
    2022-05-05
  • Java杂谈之代码重构的方法多长才算长

    Java杂谈之代码重构的方法多长才算长

    关于代码重构的理解:在不改变软件系统/模块所具备的功能特性的前提下,遵循/利用某种规则,使其内部结构趋于完善。其在软件生命周期中的价值体现主要在于可维护性和可扩展性
    2021-10-10
  • redis与ssm整合方法(mybatis二级缓存)

    redis与ssm整合方法(mybatis二级缓存)

    本文给大家介绍redis与ssm整合方法(mybatis二级缓存)。主要是利用redis去做mybatis的二级缓存,mybaits映射文件中所有的select都会刷新已有缓存,如果不存在就会新建缓存,所有的insert,update操作都会更新缓存
    2017-12-12

最新评论