共计 3816 个字符,预计需要花费 10 分钟才能阅读完成。
本文导读
很多人常常分不清 UTF- 8 编码和 UTF-16 编码,或常常会问 ”Unicode 编码和 UTF- 8 编码有什么区别分割 ”,”Java 的外码内码又是什么货色 ”,这篇文章次要做一个对于编码常识的简略扫盲,包含对一些常见概念混同进行辨别解说。
根底概念
咱们常常提到的对于编码的概念能够粗略划分为两类:
- 字符集:将一个字符映射为某个惟一的数字(码值),如字符 A 在 ascii 码中映射为 65
- 字符编码:将字符集用程序(字节)示意的一套规定,能够认为字符编码是字符集在计算机上的一种实现形式,如 utf- 8 和 utf-16 都是 unicode 码的实现形式。
Unicode 字符集介绍
Unicode 字符集一开始提出的时候,认为码值范畴为 0 -65535(0-FFFF,这一段区域也被称为 Basic Multilingual Plane, 简称BMP)就能够示意所有的字符,但随着时代倒退,0-FFFF 也不够包容所有字符,因而Unicode 划出了一个代理区:D800-DFFF,Unicode 标准规定 U +D800 – U+DFFF 的值不对应于任何字符。这也是为什么有些人说:有些字符须要用两个 Unicode 字符去示意的起因。
目前 Unicode 的编码空间为 0 -10FFFF,依据第一段落能够得悉,当某个字符的 Unicode 码值落在 0 -FFFF 时,则只用一个 Unicode 字符即可示意,否则就会用两个。
UTF- 8 编码介绍
Utf- 8 全称为 8 -bit Unicode Transformation Format,是一种针对 Unicode 字符集的可变长编码,不同的 Unicode 码点会应用不同的字节数去存储,如 ascii 码(都小于 128)则会应用 1 个字节去存储,一些罕用字符(如局部中文)会应用 2~3 个字节去存储,这有一些劣势,首先对于 ascii 码齐全兼容,且对于某些场景(只存在 ascii 码)编码后占用空间少,毛病也很显著,当遇到的都是须要占用 3 个字节存储的 Unicode 码点时,则会消耗更大的空间。
utf- 8 编码的根本规定如下:
Unicode 码范畴 | Utf- 8 编码格局 |
---|---|
0x0000-0x007F(0~127) | 0xxxxxxx |
0x0080-0x07FF(128~2047) | 110xxxxx 10xxxxxx |
0x0800-0xFFFF(2048-65535) | 1110xxxx 10xxxxxx 10xxxxxx |
0x10000-0X10FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
首先将 Unicode 码用二进制示意,而后依据 其所属的 Unicode 范畴对应的 Utf- 8 编码格局 截取最初几位。
如 0x0000-0x00FF 对应的 Utf- 8 编码格局是 0xxxxxxx,则截取最初 7 位(无效示意位其实也只有 7 位,因为从右往左第 8 位肯定是 0 ),对应填到 x 中。
同理,0x0080-0x07FF(0000 0000 1000 0000 — 0000 0111 1111 1111),则截取后 11 位(无效示意位其实只有 11 位,因为从右往左第 12 位肯定是 0 ),对应填到 x 中。
0x0080-0xFFFF(0000 1000 0000 0000 — 1111 1111 1111 1111),截取后 15 位(无效示意位其实只有 16 位,因为从右往左第 17 位肯定是 0 ),对应填到 x 中。
0x100000-0X10FFFF(0000 0001 0000 0000 0000 — 0001 0000 1111 1111 1111 1111),截取后 21 位(无效示意位其实只有 21 位,从右往左第 22 位肯定是 0 ),对应填到 x 中。
因而,咱们能够失去所有 Unicode 的 Utf- 8 编码规定。
UTF-16 编码介绍
在 Unicode 字符集中讲到,Unicode 字符集存在一个拓展区域:D800-DFFF,用于示意码点在 0x10000-0x10FFFF 范畴的字符。当碰到某个在该范畴内的 Utf-16 字符,须要再读一个 Utf-16 字符,将两个 Utf-16 字符组合示意一个 Unicode 字符。
Unicode 码范畴 | Utf-16 编码格局 |
---|---|
0x0000-0xFFFF(0~65535) | 应用 2 个字节存储 |
0x10000-0x10FFFF(65536~) | 应用 4 个字节存储,须要利用上述提到的代理区 |
接下来将 Unicode 码用二进制示意,尝试将它用 Utf-16 编码格局进行编码。
对应 0x0000-0xFFFF 范畴的 Unicode 码,间接将这 16 为对应填入两个字节(恰好 16 位)就能够失去 Utf-16 编码。
而对应 0x10000-0x10FFFF 的 Unicode 码,须要有一些非凡解决:
- 取后 20 位(减去 10000),将这 20 位数字分为高 10 位和低 10 位,高、低 10 位的范畴即为 0 -0x3FF(00 0000 0000 — 11 1111 1111);
- 将高位加上 0xD800,失去值范畴为 0xD800—0xDBFF,将低位加上 0xDC00,失去值范畴为 0xDC00—0xDFFF;
- 将高位解决后的值(又称 前导代理 )放在前 2 个字节中,将低位解决后的值( 后导代理)放在后 2 个字节中。
通过解决,前导代理和后导代理恰好占满了 0xD800—0xDFFF 这一段代理区域,这样解决的一个长处在于,看到每一个 Utf-16 编码,能够很分明地确定它是属于前导代理、后导代理还是除此以外的 BMP 区域中的 Unicode。
MUTF- 8 编码介绍
MUTF-8(Modified UTF-8)编码,能够认为是对 UTF-16 编码的再编码。它的编码方式与 UTF- 8 编码十分类似,只须要记住某些不同的状况,其余都与 UTF- 8 编码统一。
具体的不同状况有二:
- 对于 Unicode 的 0 码点,UTF- 8 间接应用 1 个字节去存储(0000 0000),而 MUTF- 8 会应用 2 个字节去存储,最初存储的值为 0xC080(1100 0000 1000 0000)。
- 对于 0x10000-0x10FFFF 这块区域的 Unicode 码,之前提过 UTF- 8 是应用 4 个字节去存储,而 MUTF- 8 是对 UTF-16 的再编码,所以MUTF- 8 是对 UTF-16 编码的两个字符别离用 3 个字节去编码(因为这段区域的 Unicode 码值转为 UTF-16 编码后前导代理和后导代理的范畴是 0xD800—0xDFFF,显著大于 0x0800),共须要 6 个字节。
所以网上常常会提到 UTF- 8 编码,又提到用 1—6 个字节去编码,其实说的是 MUTF- 8 编码。
Java 的内码与外码
Java 的内码是 UTF-16,外码是 MUTF-8。那什么是内码和外码呢?
内码:程序外部应用的字符编码,如 java 的 char,所以 java 的 char 是 2 字节 16 位;
外码:程序内部交互时应用的字符编码,如 class 文件。
在深刻了解 Java 虚拟机第三版 6.3.2 节中,咱们能够得悉其实 Java 的字符串常量(如 String str=”hello world”)都是以 CONSTANT_Utf8_info 类型存在常量池中的,class 文件的编码是 MUTF-8,所以 CONSTANT_Utf8_info 中存储的依据不同的实现个别是存储 MUTF- 8 字节数组或 UTF-16 字符数组,每次 构建时 java.lang.String 对象时,须要通过 MUTF-8=>UTF-16 的一个编码转换将外码转为内码,再将其塞到 char 数组(value)中。
在 Java API 层对字符串的操作,其实个别也是对 UTF-16 字符的操作,如 charAt 函数:
public char charAt(int index) {if ((index < 0) || (index >= value.length)) {throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}
charAt 函数理论是返回了一个 char,所以是返回了一个 UTF-16 字符,它不肯定是一个残缺的 Unicode 码点。
当然,在 Java API 层也能够应用 getBytes(”UTF-8″)则是返回 UTF- 8 编码的字节数组。
总结
依据下面文章的解说,咱们就能够讲清楚上面几个经常遇到的问题:
- Unicode 编码和 UTF- 8 编码的区别?其实 Unicode 只是字符集,而 UTF- 8 是该字符集在计算机中的编码表示。
- 为什么说 UTF- 8 是 1~6 个字节?这里的 UTF- 8 其实在指 MUTF- 8 编码,MUTF- 8 应用 1~3 个字节对 UTF-16 编码进行再编码,所以就产生了应用 6 个字节示意一个 Unicode 字符的状况。
- Java 的 char 到底占用几个字节?Java 内码应用的是 UTF-16 编码,UTF-16 对每个 Unicode 字符应用 2 或 4 个字节进行编码,所以对每个 char 单位,其实是占用了 2 个字节。
另外,java 虚拟机对字符串的示意或解决很多都是应用的 UTF-16 编码或 MUTF- 8 编码,而 UTF- 8 编码个别是显式通过 Java API 层的 String.getBytes("UTF-8")
函数失去。
平时应用时,如果只用 Java 语言开发个别不会有什么乱码问题,但如果本人想手动实现一个 Java 虚拟机,或是要通过 JNI 做一些事件的时候,就须要去理解一下 Java 的这些编码常识了。如笔者之前参加的我的项目,用 go 解决 Java 虚拟机的编码问题就很头大,因为 Go 这边默认应用的是 UTF- 8 编码,所以如果在实现常量池的过程中用 Go 的 string 去存储 java.lang.String 的理论内容,则可能呈现一些奇怪的乱码问题。