一文讲通Unicode规范、UTF-8与UTF-16编码及在Java中的验证

结论:

  1. java中的char类型表示的是UTF-16编码中的一个码元,所以一个char类型只能表示第0级别(BMP)中的符号,其他级别中的符号需要用两个char变量才能表示。char用U+xxxx的方式赋值时,规定xxxx的范围是[0000->FFFF],所以补充平面中的符号是不能直接通过U形式的赋值到一个char变量直接得到的(因为需要两个变量)。
  2. java中的char及它的包装类、StringStringBuffer类使用UTF-16编码,在字节码中使用改进的utf-8编码,而在字节码加载到内存中执行的时候,又把缩略码转化为UTF-16的表示形式。字节码改变形式可能是因为出于对节省空间缩小文件体积的考量。
  3. 关于javaString内部存储方式发生变化的说明:
    • jdk9之后,String的内部存储由char[]改为了byte[]和一个标志位,这个改变有利于节省空间。

一、Unicode

Unicode是为了统一不同字符间的交互与编码问题而制定的一种规范,旨在对世界上所有可数字化的书写系统中的所有文字进行文本编码。

Unicode规范中,每一个可书写字符都有一个唯一码点(code point),也就是说每一个字符都由一个码点唯一确定。

Unicode可使用多种编码方式进行处理和存储二进制数据,Unicode标准中主要 定义了三种编码方式:UTF-8UTF-16UTF-32,其中UTF-8的使用最为广泛。

二、UTF-8编码规则

练习打字的时候有一个常识,即汉字的大部分文本都是由常见的前500字组成的,如果要练打字速度的话,只需要把常见的前500个字练好就可以了。为了保险起见,可以练习常见的前1500个字。也就是"二八定律"。

同样,在编码中,数值较低的码点代表的符号出现的频率通常较高,因此低码点的数字需尽量的使用较少的字节进行编码。ASCII码的码点范围是从0-127共128个数,在UTF-8编码中,ASCII码的编码需与其自身相同。

UTF-8可以使用一个字节、二个字节、三个字节与四个字节进行编码。这四种编码方式的表示范围与编码方式如下所示。

从这个表中可以看出:

  1. 使用一个字节编码时,最高位取0,用其余七个数来表示具体的值,正好是128个,与ASCII码范围完全一致。
  2. 首字节的前几位已固定,如果1的个数是n,则使用n个字节来编码。非首字节以10开始。

具体的编码规则为:

以'中'的编码为例:

通过java程序可以得出'中'的码点为0x4E2DUTF-8编码为:U+E4B8AD。 与按照编码规则手动计算的结果一致:

java中对于char类型和String类型使用的是utf-16编码,在字节码文件中常量池中使用的是utf-8的缩略编码,3个字节的缩略编码与正常编码是一样的,所以'中'字在字节码文件中一定也是正常utf-8的三字节表示方式;python中使用的是utf-8编码;C语言通过设置编码格式也可以使得char类型使用utf-8编码。

java中的验证

字节码文件中可以看到第一行为sipush 20013,即将20013入栈。下一个执行的指令为istore_1,即从操作数栈中弹出栈顶的int(此处就是20013),然后将弹出的数存储到局部变量表索引为1的位置。

查看局部变量表可知,索引为0的位置存储的是main方法的形参args,1位置存储的是局部变量c,即将20013赋值给c变量。此处的20013转换为16进制就是0x4e2d.

三、UTF-16编码规则

Unicode中的Plane(平面)

Unicode标准中,平面是由65536 <math xmlns="http://www.w3.org/1998/Math/MathML"> ( 2 16 ) (2^{16}) </math>(216)个连续码点组成的组。将所有码点划分为17个平面,对于16进制的00至10。其中第0个平面称为BMP(Basic Multilingual Plane),包含了最常用的字符。其余的16个平面称为补充平面。这17个平面中的最后一个码点为:U+10FFFF.

UTF-16编码的长度也是可变的,可以使用一个或者两个16-bit的代码单元(也就是2字节或者4字节)。

在这个概念下,第0个级别的范围为U+0000->U+0FFFF,将这个级别分为三个范围:

  1. U+0000->U+D7FF
  2. U+D800->U+DFFF
  3. U+E000->U+FFFF

编码规则为:

  1. BMP中,用两个字节表示

  2. 在补充平面中,用四个字节表示

    • 补充平面的表示范围为:U+10000->U+10FFFF,这个范围内的码点编码时需要先减去U+10000,减完之后,范围就会变为[U+0000->U+FFFFF],也就是2^{20}个数,需要用20位来表示。
    • 把这20位拆成高10位和低10位,高10位需要加上0xD800。这16位的表示范围就会变为:[U+D800->0xDBFF]对应于0级别的第二个范围的前半部分。
    • 低10位加上0xDC00,表数范围就会变为:[U+DC00->DFFFF],对应于0级别的第二个范围的后半部分。与高10位组合出来的范围恰好完整构成0级别的整个第2段范围。

java中的验证

相关推荐
十八旬3 小时前
苍穹外卖项目实战(day7-1)-缓存菜品和缓存套餐功能-记录实战教程、问题的解决方法以及完整代码
java·数据库·spring boot·redis·缓存·spring cache
Java微观世界3 小时前
匿名内部类和 Lambda 表达式为何要求外部变量是 final 或等效 final?原理与解决方案
java·后端
SimonKing3 小时前
全面解决中文乱码问题:从诊断到根治
java·后端·程序员
你三大爷4 小时前
再探volatile原理
java
2301_781668614 小时前
Redis 面试
java·redis·面试
郑洁文4 小时前
基于SpringBoot的天气预报系统的设计与实现
java·spring boot·后端·毕设
沃夫上校4 小时前
MySQL 中文拼音排序问题
java·mysql
Dcs4 小时前
用 Python UTCP 直调 HTTP、CLI、MCP……
java
快乐肚皮5 小时前
fencing token机制
java·fencing token