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,如果出现了这个序列,这个字符就由这个序列和后面两个字节组成的序列定义,不再多说,详情见下。
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完整的表
============= End