一文讲通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中的验证

相关推荐
青衫码上行32 分钟前
【Java Web学习 | 第1篇】前端 - HTML
java·前端·学习
阿金要当大魔王~~1 小时前
uniapp img 动态渲染 的几种用法
java·服务器·前端·1024程序员节
摸鱼的老谭1 小时前
Java 25 中的最佳新特性
java·1024程序员节
lang201509281 小时前
Spring Boot健康检查全解析
java·spring boot·后端
我是华为OD~HR~栗栗呀2 小时前
华为OD-Java面经-21届考研
java·c++·后端·python·华为od·华为·面试
考虑考虑2 小时前
流收集器
java·后端·java ee
今天背单词了吗9802 小时前
Spring Boot+RabbitMQ 实战:4 种交换机模式(Work/Fanout/Direct/Topic)保姆级实现
java·spring·中间件·rabbitmq·1024程序员节
九皇叔叔3 小时前
Java循环结构全解析:从基础用法到性能优化
java·开发语言·性能优化
流星5211223 小时前
GC 如何判断对象该回收?从可达性分析到回收时机的关键逻辑
java·jvm·笔记·学习·算法
csdn_aspnet3 小时前
Java 圆台体积和表面积计算程序(Program for Volume and Surface area of Frustum of Cone)
java