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

VSole2022-05-17 15:50:28

如果你不是 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 使用四个字节存储,具体来说就是:将字符编号的所有比特位分成两部分,较高的一些比特位用一个值介于 D800~DBFF 之间的双字节存储,较低的一些比特位(剩下的比特位)用一个值介于 DC00~DFFF 之间的双字节存储。


但是在 Java 中,一个字符(char)就是 2 个字节,占 4 个字节的字符,在 Java 里也是用两个 char 来存储的。


而 String 的各种操作,都是以 Java 的字符(char)为单位的,charAt 是取得第几个char,subString 取的也是第几个到第几个 char 组成的子串,甚至 length 返回的都是 char 的个数。


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

-End-


最近有一些小伙伴,让我帮忙找一些 面试题 资料,于是我翻遍了收藏的 5T 资料后,汇总整理出来,可以说是程序员面试必备!所有资料都整理到网盘了,欢迎下载!



点击?卡片,关注后回复【面试题】即可获取

在看点这里好文分享给更多人↓↓

stringchar
本作品采用《CC 协议》,转载必须注明作者和本文链接
无意中看到ch1ng师傅的文章觉得很有趣,不得不感叹师傅太厉害了,但我一看那长篇的函数总觉得会有更骚的东西,所幸还真的有,借此机会就发出来一探究竟,同时也不得不感慨下RFC文档的妙处,当然本文针对的技术也仅仅只是在流量层面上waf的绕过。Pre很神奇对吧,当然这不是终点,接下来我们就来一探究竟。前置这里简单说一下师傅的思路部署与处理上传war的servlet是?
RCE回显技术在20年突然火爆全网,这里学习跟进一下。
可以使用 '|"|}|) 等特殊字符进行检测,除了正常的参数提交外,注入的位置也可能存在于 HTTP header 中,比如 X-Forwarded-For、User-Agent、Referer、Cookie 中。不同数据库的报错内容:
在最近的研究和分析工作中,我们发现通过Google搜索实现的恶意广告活动有所增加,目前我们也在跟踪和分析其中相关的威胁行为者,并对他们在绕过安全检测并实现攻击链时所使用的技术进行了深入研究。
1 介绍XSS 是网络中最常见的漏洞,再配合其它的攻击手段往往能轻易敲开服务器的大门。在各大漏洞平台中,XSS 漏洞也是数量最多的。传播记录描述了数据从 source 到 sink 的变化过程,传播记录的完整性直接影响 XSS 的检测结果。众所周知,触发 XSS 漏洞的字符串往往是来自于不同 source 的字符的组合,如果不能完整记录数据变化的过程或做不到字符级别的描述,那将会产生大量的漏报。做好以下这些 API 的记录就可以最大程度保证传播记录的完整性。
研究人员发现越来越多的钓鱼攻击开始使用去中心化的IPFS网络。
介绍Runtime 是一系列采用 C++ 语言编写的功能方法,它实现了大量 JavaScript 运行期间需要的 native 功能。本文分析 Runtime_StringToArray 方法的源码和重要数据结构,讲解 Runtime_StringToArray 方法的触发条件。
现在只对常读和星标的公众号才展示大图推送,建议大家把潇湘信安“设为星标”,否则可能看不到了!0x00 前言对国外某地产公司的一次测试,测试过程中每一步都有阻碍,不像以往的一帆风顺,对其中涉及的一些点进行一个简单的记录,码较厚,见谅。
Thymeleaf SSTI漏洞分析
2021-11-11 12:56:34
要了解SSTI漏洞,首先要对模板引擎有所了解。下面是模板引擎的几个相关概念。 模板引擎(这里特指用于Web开发的模板引擎)是为了使用户界面与业务数据(内容)分离而产生的,它可以生成特定格式的文档,用于网站的模板引擎就会生成一个标准的文档。 模板引擎的本质是将模板文件和数据通过模板引擎生成最终的HTML代码。 模板引擎不属于特定技术领域,它是跨领域跨平台的概念。 模板引擎的出现是为了解决前后端分离
VSole
网络安全专家