查看原文
其他

【285期】JDK9 为何要将 String 的底层实现由 char[] 改成了 byte[]?

Java精选 2022-08-09

点击上方“Java精选”,选择“设为星标”

别问别人为什么,多问自己凭什么!

下方有惊喜,留言必回,有问必答!

每一天进步一点点,是成功的开始...

如果你不是 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 中的字符只用一个字节就能表示,也得占用两个字节。


而实际开发中,单字节的字符使用频率仍然要高于双字节的。

另外,推荐下 Spring Cloud 的微服务实战开源项目:

https://gitee.com/yoodb/jingxuan-springcloud

当然了,仅仅将 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 个字节来存储字符。

另外,推荐下 Spring boot 的实战开源项目:

https://gitee.com/yoodb/jing-xuan

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

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

  • 另外,公众号Java精选,回复java面试,获取更多相关面试题资料,支持在线刷题。


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


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

作者:basic13

https://www.zhihu.com/question/447224628

公众号“Java精选”所发表内容注明来源的,版权归原出处所有(无法查证版权的或者未注明出处的均来自网络,系转载,转载的目的在于传递更多信息,版权属于原作者。如有侵权,请联系,笔者会第一时间删除处理!

------ THE END ------

精品资料,超赞福利!


3000+ 道面试题在线刷,最新、最全 Java 面试题!

期往精选  点击标题可跳转

【277期】如何写好 Java 业务代码?这也是有很多规范的!

【278期】Spring Boot 巧用 @Async 提升接口并发能力

【279期】Spring Boot 服务监控机制,总算整明白了!

【280期】Spring Boot 利用 Redis 实现接口限流

【281期】面试官问:淘宝七天自动确认收货,可以怎么实现?

【282期】Spring Boot 项目使用 Disruptor 做内部消息队列,没看错!

【283期】面试官问:高并发场景下,如何保证全局唯一分布式 ID 生成?

【284期】高逼格的别样 SQL 写法:行行比较!

 技术交流群!

最近有很多人问,有没有读者交流群!想知道如何加入?方式很简单,兴趣相投的朋友,只需要点击下方卡片,回复“加群”,即可无套路入交流群!

文章有帮助的话,在看,转发吧!

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存