Unicode、UTF-八、UTF-1六、MUTF-八、Java编码扫盲

本文导读

不少人常常分不清UTF-8编码和UTF-16编码,或常常会问"Unicode编码和UTF-8编码有什么区别联系","Java的外码内码又是什么东西",这篇文章主要作一个关于编码知识的简单扫盲,包括对一些常见概念混淆进行区分讲解。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编码的基本规则以下:code

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编码格式截取最后几位。orm

如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中。utf-8

0x0080-0xFFFF(0000 1000 0000 0000 — 1111 1111 1111 1111),截取后15位(有效表示位其实只有16位,由于从右往左第17位必定是0),对应填到x中。ci

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码,须要有一些特殊处理:

  1. 取后20位(减去10000),将这20位数字分为高10位和低10位,高、低10位的范围即为0-0x3FF(00 0000 0000 — 11 1111 1111)
  2. 将高位加上0xD800,获得值范围为0xD800—0xDBFF,将低位加上0xDC00,获得值范围为0xDC00—0xDFFF;
  3. 将高位处理后的值(又称前导代理)放在前2个字节中,将低位处理后的值(后导代理)放在后2个字节中。

经过处理,前导代理和后导代理刚好占满了0xD800—0xDFFF这一段代理区域,这样处理的一个优势在于,看到每个Utf-16编码,能够很清楚地肯定它是属于前导代理、后导代理仍是除此之外的BMP区域中的Unicode。

MUTF-8编码介绍

MUTF-8(Modified UTF-8)编码,能够认为是对UTF-16编码的再编码。它的编码方式与UTF-8编码很是类似,只须要记住某些不一样的状况,其余都与UTF-8编码一致。

具体的不一样状况有二:

  1. 对于Unicode的0码点,UTF-8直接使用1个字节去存储(0000 0000),而MUTF-8会使用2个字节去存储,最后存储的值为0xC080(1100 0000 1000 0000)。
  2. 对于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的实际内容,则可能出现一些奇怪的乱码问题。

相关文章
相关标签/搜索