IT七剑客 IT七剑客
首页
wresource
郭霖
孤寒者
IT邦德
沉默王二
老麦
stackoverflow
GitHub (opens new window)
首页
wresource
郭霖
孤寒者
IT邦德
沉默王二
老麦
stackoverflow
GitHub (opens new window)
  • Java基础语法

  • 程序人生

  • 实用工具

  • Java重要知识点

    • Java中常用的48个关键字和2个保留字
    • 深入理解Java中的注解
    • 深入剖析Java中的拆箱和装箱
    • 先有Class还是先有Object?
    • 详解Java中Comparable和Comparator的区别
    • 彻底讲明白的Java浅拷贝与深拷贝
    • Java枚举(enum)
    • 一次性搞清楚equals和hashCode
    • 大白话说Java反射:入门、使用、原理
    • 深入理解Java泛型
    • 深入理解Java中的hashCode方法
    • 深入理解Java中的不可变对象
    • instanceof关键字是如何实现的?
    • Java中int、Integer、new Integer之间的区别
    • Java命名规范,告别编码 5 分钟,命名 2 小时
    • 彻底弄懂Java中的Unicode和UTF-8编码
    • jdk9为何要将String的底层实现由char[]改成了byte[]?
      • 为什么JDK源码中,无限循环大多使用for(;;)而不是while(true)?
      • Java 方法重写 Override 和方法重载 Overload 的区别,一下子就明白了
      • Java重写(Overriding)时应当遵守的11条规则
      • Java到底是值传递还是引用传递?
      • Java不能实现真正泛型的原因是什么?
      • Java中可变参数的使用
    • Java工具

    • 数组与字符串

    • 沉默王二 Java
    • Java重要知识点
    沉默王二
    2023-01-22
    目录

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

    # jdk9为何要将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";
    
    1

    这样的,使用 Latin-1 编码,占用 4 个字节就够了。

    但对于:

    String name = "小二";
    
    1

    这种,木的办法,只能使用 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;
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    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)。

    关于字符编码,我在《Java 程序员进阶之路》里曾讲到过,想要深入了解的小伙伴查看下面的链接🔗:

    https://tobebetterjavaer.com/basic-extra-meal/java-unicode.html

    也就是说,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的世界里,就可以视为一个定长的编码。

    参考链接:https://www.zhihu.com/question/447224628


    最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关 等等等等……详情戳:可以说是2022年全网最全的学习和找工作的PDF资源了 (opens new window)

    关注二哥的原创公众号 沉默王二,回复111 即可免费领取。

    编辑 (opens new window)
    #沉默王二#Java
    上次更新: 2023/01/22, 15:00:47
    彻底弄懂Java中的Unicode和UTF-8编码
    为什么JDK源码中,无限循环大多使用for(;;)而不是while(true)?

    ← 彻底弄懂Java中的Unicode和UTF-8编码 为什么JDK源码中,无限循环大多使用for(;;)而不是while(true)?→

    最近更新
    01
    Coding 102 Writing code other people can read
    02-26
    02
    Kotlin Flow响应式编程,StateFlow和SharedFlow
    02-05
    03
    Kotlin Flow响应式编程,操作符函数进阶
    02-05
    更多文章>
    Theme by Vdoing | Copyright © 2022-2023 IT七剑客 | MIT License
  • 闽ICP备2021006579号-4
  • 闽公网安备 35012102500470号
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式