Unicode 和 UTF-8 的区别

Unicode 和 UTF-8 的区别

 

简单来说:
• Unicode 是「字符集」
• UTF-8 是「编码规则」
其中:
• 字符集:为每一个「字符」分配一个唯一的 ID(学名为码位 / 码点 / Code Point)

• 编码规则:将「码位」转换为字节序列的规则(编码/解码 可以理解为 加密/解密 的过程)


广义的 Unicode 是一个标准,定义了一个字符集以及一系列的编码规则,即 Unicode 字符集和 UTF-8、UTF-16、UTF-32 等等编码……

Unicode 字符集为每一个字符分配一个码位,例如「知」的码位是 30693,记作 U+77E5(30693 的十六进制为 0x77E5)。
UTF-8 顾名思义,是一套以 8 位为一个编码单位的可变长编码。会将一个码位编码为 1 到 4 个字节:

U+ 0000 ~ U+ 007F: 0XXXXXXX
U+ 0080 ~ U+ 07FF: 110XXXXX 10XXXXXX
U+ 0800 ~ U+ FFFF: 1110XXXX 10XXXXXX 10XXXXXX
U+10000 ~ U+1FFFF: 11110XXX 10XXXXXX 10XXXXXX 10XXXXXX

根据上表中的编码规则,之前的「知」字的码位 U+77E5 属于第三行的范围:

7 7 E 5
0111 0111 1110 0101 二进制的 77E5
--------------------------
0111 011111 100101 二进制的 77E5
1110XXXX 10XXXXXX 10XXXXXX 模版(上表第三行)
11100111 10011111 10100101 代入模版
E 7 9 F A 5
这就是将 U+77E5 按照 UTF-8 编码为字节序列 E79FA5 的过程。反之亦然。

 

计算机是在美国发明的,人家只考虑了自己,所以只有ASCII字符集(或者说ASCII编码,早期这俩是一个概念),后来其它国家也用上了电脑却没法输入自己的文字,就各自定了自家的字符集/编码,比如简体中文是GB2312,繁体中文Big5,日文Shift_JIS,韩文是EUC_KR等。这样的话中国用户(只有GB2312)不能读写日文,日本用户(只有Shift_JIS)不能读写中文...,各个国家就把自己的编码上报到ANSI(美国国家标准协会)这个组织,于是Windows记事本在保存的时候编码方式一栏就写个「ANSI」,而实际上这里写的「ANSI」只是个代称,它在英文操作系统等同ASCII,在简体中文操作系统等同GB2312,在繁体中文操作系统等同Big5...

继续说历史,各家因不同文字制定不一样的编码不利于全球化,于是两个组织出来说要把全球所有文字纳入一个字符集,Unicode这个组织搞两个Unicode字符集,ISO这个组织搞了个UCS字符集,后来两家意识到目的一致没必要搞两份,就融合了一下叫个「Unicode」。

微软把 UTF-16 称作 Unicode(因为早期的 UTF-16 编码单元可以和 Code Point 一一对应,不过现在已经不行了)

历史讲完,下面回答问题:

1,「ANSI」其实并不是具体的某一种编码方式,它是动态的;「Unicode」其实是UTF-16LE(LE指小端,大小端简单说就是编码的时候文字头朝前还是屁股朝前,你想咋样都成);「Unicode BE」其实是UTF-16BE;「UTF-8」其实是UTF-8 BOM(BOM的存在是为了区别UTF-16LE、UTF-16BE和UTF-8,因为这3种编码方式共存过)。

2,如上文所说,GB2312是早期中国给自己的简体中文制定的编码,后来加入繁体发展成了GBK,又后来融入日韩文字发展成GB18030(均向前兼容)。

 

------------------------------

 

简单来说

Unicode 是字符集

(里面几乎包含所有已知字符,并对他们进行从1,2,3,4,......编号)

 

UTF-8 是对字符编号的储存方式,是一种多字节表示方法

(在电脑中最简单的储存单元是字节,一个字节由8个二进制编码构成)

(字符储存方式指,如何用字节,表示这一般来说1,2,3,4.....的字符)

//注,有些(字符集+编码方式)不是顺序编码,比如GBK,下文分析

 

UTF-8与Unicode关系,UTF-8一般是对unicode字符编码的

 

储存方式理解

(一个字节用xxxxxxxx表示, 这里采用 unicode / UTF-8 / non-BOM / Little-endian)

啥字符 Unicode中是第几个 字节如何表示

'\0' (空字符) 0 00000000

'\1' (标题开始字符) 1 00000001

'\2'(正文开始字符) 2 00000010

//注:某些字符无法在屏幕上打印出来的,用于段落标记,比如换行符

........(省略一些非打印字符)

'A' 81 01010001

'B' 82 01010010

'C' 83 01010011

对你想的没错,就是ASCII对应表格,前128个如图

//那么你也发现了,一个字节最多表示256个字符,那明显无法表示所有的(unicode 10万+)个,那就要用两个以上的字节表示咯,先接着往下看。

 

啥字符 Unicode中是第几个 字节如何表示

'\0' (空字符) 0 00000000

'汉' 27721 11100110 10110001 10001001

'字' 23383 11100101 10101101 10010111

你看,就用很多个字节表示咯,补充下UTF-8属于多字节编码,除了多字节还有宽字符编码

如果你想知道更多的对应汉字编码和UTF-8 / UCS-2/4 怎么表示,下面的网站可以没事转转玩。

Escaped Unicode, Decimal NCRs, Hexadecimal NCRs, UTF-8 Converter

 

嗯,来了解下 UTF-8 是如何编码的,我假设你有大把的时间,下面我们来讲讲历史咯

 

1. 上面的ASCII - American Standard Code for Information Interchange 表格也是个字符集,和Unicode前 127 个兼容,其实大部分字符集这都是兼容的。

2. 一开始为了表示更多的字符,人们发明了一些字符集,用完了一个字节能表示最多的256个字符的,其中有代表力的 ASCII-extended 能包括绝大部分西欧国家的字符。

当然一到 汉字,日文,韩文,这些大字符集,一个字节表示不完了,就用到了多字节和宽字符。

 

3.如何表示前要先编码,Unicode就是一个把这些字符从 1- 10万+ 编码的对应表格

//作者不想讨论Unicode和UCS GAYGAY 的历史,有兴趣自行百度

 

4.然后 贝尔实验室的 Ken Thompson 和 Rob Pike 想出了一种表示方法叫 UTF-8

//不是发明电灯泡的贝尔,是发明C语言的贝尔实验室,学过C语言的知道 K&R 白皮书吧,K 指的就是 Ken Thompson

 

5. UTF-8 编码规则

码点范围(十六进制) UTF-8序列

000000~00007F 0xxxxxxx

000080~0007FF 110xxxxx 10xxxxxx

000800~00FFFF 1110xxxx 10xxxxxx 10xxxxxx

010000~10FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxxx

UTF-8将码点(字符编号)分为几组,表中用 x 表示,并将每组分配不同的字节,最简单的是0~7F中范围的(和ASCII兼容)。

码点在80~7FF范围内(包括所有Latin-1字符)内,将码点分为两组,一组5位,一组6位,前5位组前缀为110,后6位组前缀为10

例如字符 ä 码点为 E4 (16进制)或者 11100100(二进制),在UTF-8中表示为11000011 10100100,连起来就是 00011100100 表示 E4,对应 ä

 

6. UTF-8的特点

(1) 与ASCII 前 128个字符兼容,都用一个字节表示,表示编码更后面的字节时,才采用更多字节

(2)开始序列有多少个 1 表示用来表示这个字符的 字节个数

(3)用于表示一个字符的字节中,初开始字节,后续字节全用 10 开头,这使得他们无法成为另一个字符表示的开始

 

7. 对多字符的补充 - GBK 编码的探索

有些字符编码方式没有对应字符集,其本身的字节序列就可以直接对应某个字

//在Windows中,有个叫非Unicode地区设置,玩过日文游戏的老司机都应该懂如何转区的

一般这些(字符集+编码方式)只能用于特定的地区,比如日本,中国大陆(简),中国台湾(繁)

 

大概的地区编码对应

中国大陆 GBK(国家信息标准交换码) Leading-Bytes

中国台湾 BIG5(大5码) Leading-Bytes

日本 Shift-JIS(只是常见) Leading-Bytes & Shift-Status

 

这些编码采用,Leading-Byte 和 Shift-Status 等方式编码

 

Leading-Bytes,出现了这些特定的字节,那么这个字符用这个字节和接下来后面的一个字节表示

// 或者接下来几个字节,GBK采用最多4个字节,但是,windows只支持到2个字节,说实话,它两个字节能编的量都没用完呢,反正别管就是了

GBK -Leading_bytes 0x81-0xfe

BIG5 -Leading_bytes 0x81-0xfe

Shift-JIS -Leading_bytes 0x81-0x9f & 0xe0-0xef

 

Shift-Status,出现了这些特定的字节,那么会改变后续所有字节翻译的对应字符

请参考Shift_JIS日本电脑系统常用的编码表-Wikipedia

//主要是规则太复杂,反正咱用不到

 

具体GBK,BIG5,Shift-JIS如何与Unicode转化,和字符对应字节表示参考下列文件

(由于microsoft原来的这些文件都是Page Not Found,所以原网站下不到了,稀有咯)

GBK - 编码方式以及Unicode对应 - OneDrive 文件

BIG5 - 编码方式以及Unicode对应 - OneDrive 文件

Shift-JIS - 编码方式以及Unicode对应 - OneDrive 文件

//什么OneDrive打不开?那你需要一个VPN,或者改hosts了,别问我咋改hosts,STFW

204.79.197.213 OneDrive API and File Pickers

2.17.58.51 api.onedrive.live.com

204.79.197.217 onedrive.live.com

204.79.197.217 skyweb.skyprod.akadns.net

204.79.197.217 webedgegeo.skyprod.akadns.net

104.44.88.28 skyapi.onedrive.live.com

13.107.42.11 outlook.live.com

//以上与本答案无关,送给改hosts的朋友

 

8. 简单来说,这些本地化字符集+编码很局限,只能在特定地区使用

还有一句话,如果是UTF-8/16,UCS-2/4什么的,只要不是地区特定的(字符集+编码方式),一般都是对Unicode字符集编码,如何分别是那种编码呢,见下文BOM 分析

 

9. 扩展,宽字符编码方式 - (UTF-16/UCS-2)

相对于多字节编码,宽字符编码就简单多了,一句话就是。

 

全部字符,用两个字节表示就行了

//当然也有全部4个字节表示的 UTF-32/UCS-4,主要是还是不够表示完......

 

这就是UCS-2编码方式,是不是很简单,编码也和舒服,是第几个字符就对应翻译成字节就行了

 

字符 Unicode第几个 字节序列(little-endian)

'汉' 27721 (6C49) 49 6C

'字' 23383 (5B57) 57 5B

//你会发现在小端little-endian中字节序列是反的,为啥我不在一个字节中书写也反着呢?

因为在一个字节里,没必要区别存储的排序方式,它爱咋咋排序都行,读取出来都不会变的

 

大端(Big-Endian)和小端(Little-Endian)就是指,字节储存方式的排序,

大端,先储存高位的数值,代表有,摩托罗拉处理器

小端,先储存低位的数值,代表有,Intel处理器

//这两种储存方式没有好坏分别,我也不想扯敲鸡蛋的由来故事

 

注释

UCS-2和UTF-16是一个东西

UCS-4和UTF-32是一个东西

 

10. 区别与UCS-4/UTF-32,UCS-2/UTF-16还是没法用两个字节表示完所有的Unicode字符集,

但是基本的字符(基本平面语)都可以表示,那剩下的部分呢,也有像GBK,BIG5,Shift-JIS,一样,有Leading - Word,如果出现了这个序列,这个字符就由这个序列和后面两个字节组成的序列定义,不再多说,详情见下。

UTF-16/UCS-2 - Wikipedia

 

11. BOM - Bytes Order Mark - 字符前面标记编码方式的东西

(nothing) - ANSI (一般指本地编码,或UTF-8无BOM的时候)

EF BB BF -UTF-8

FE FF - UTF-16/UCS-2 (little endian)

FF FE -UTF-16/UCS-2 (big endian)

FF FE 00 00 - UTF-32/UCS-4 (little endian)

00 00 FE FF - UTF-32/UCS-4 (big endian)

没错就是这个东西,它写在文本的前面,如果出现了就相当于告诉了编码方式,好像是个啥ISO提出的。

这些字节就像Leading-bytes一样,在各字符集都是保留了不对应任何字符的。

 

注:如果没有BOM,那么你打开一个文件,程序就会猜测,感觉你这文件的编码方式是啥,如果猜错了,那么就是乱码了

 

12. 最后写给C标准库的程序员,关于rewind函数(仅是个人感想)

//在VS 2013测试下
#include <stdio.h>

void func(void)
{
   FILE *fp = fopen(...);
   //当你打开一个文件,一般是无法读出BOM的
   rewind(fp);
   //当你用rewind函数后,它就是从BOM开始读取的了
   //你要是没意识到这点,就炸了
}

最后给个Unicode完整的表

Unicode 表 - OneDrive

 

============= End

 

posted @ 2019-12-20 22:29  lsgxeva  阅读(3311)  评论(1编辑  收藏  举报