字符集和字符编码
什么是字符集
ASCII(American Standard Code for Information Interchange)
1 | man ascii |
欧洲语系 ISO8859
- ISO8859-1英语、法语、德语;ISO8859-5 俄语
中国的GB2312和GBK,以及中国台湾的Big5
多语系 Unicode
- 最初的目的是把世界各地的语言都映射到16位空间
- 8位转换为16位,称为UCS-2 (2 byte Universal Character Set), 也叫做Basic Multilingual Plane (BMP)
- 16位到21位,有效编码区间0 ~ 0x10ffff
The Unicode standard describes how characters are represented by code point
什么是编码方式
取“鄢”这个字的Unicode编码
1 | echo "鄢" | native2ascii -encoding utf8 |
一段查看汉字编码的Java代码
1 | import java.nio.charset.Charset; |
UTF-8变长编码方式
1 | \u9122 |
- 从程序运行的结果来看,
UTF-8
中,“鄢”占据了3个字节,且在计算机中的补码表示分别为-23, -124, -94
; \u9122
占据2个字节,但是带入UTF-8
编码提供的1110xxxx 10xxxxxx 10xxxxxx
模板中就变成了3个字节,我们是从低位带入模板的,x
的个数刚好是16位、2个字节;UTF-8
编码除了和ASCII码兼容部分(以0开头,0xxxxxxx
),其余都遵循一个简单的标准:- 其模板遵循
110xxxxx 10xxxxxx
,高位1的个数即是总的字节数,这里110xxxxx
中11表示总共有两个字节; - 低位的
10xxxxxx
始终以10开头; - 这样的编码方式,使得程序清楚知道那个地方是字符开始的地方,所以才说
UTF-8
的单字节的编码方式。
- 其模板遵循
但是UTF-8也有自己的缺点
- 浪费内存,几乎所有的汉字都占三个字节
- 随机访问,同字符串的长度成正比
Tips:
正数的补码就是其本身
负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1
大端字节序和小端字节序(网络字节序和主机字节序)
我们知道UTF-16
的目的和Unicode本来的目的是一致的 —— “把世界各地的语言都映射到16位空间”。
以\u9122
为例,我们运行程序的结果应该是2个字节,但是事实上是4个字节(*How many bytes: 4 What are they: [-2, -1, -111, 34]*),这多出来的2个字节是怎么回事?
这里就是字节序在作祟。在内存存储时,如果最低有效位在最高有效位的前面,则称小端序;反之则称大端序。大端字节序又称网络字节序。
举个例子
1 | \u9122 ;;;->鄢 |
我们观察一下返回的4个字节中的前两个字节-2, -1
,它们其实不是字符“鄢”的一部分,它们是Unicode中的大端字节序的标识BOM(Bytes Order Marks)
1 | 0xfeff |
Unicode在对待字节序时,采取了大端和小端序共存的方式。0xfeff
这个编码在对调字节序后得到的0xfffe
在Unicode不存在,Unicode借助这个特点来判断读入的字符到底是大端序还是小端序。
另外,从程序中可知,Java会默认将读入的字符串当做大端序处理;所以当我们明确指定UTF-16BE
后,程序就会返回2个字节的结果了。
UTF-16不足以表示世界所有的字符
正如前面提及的UCS-2 (2 byte Universal Character Set)不足以表示世界上所有的字符,比如“🐶”这种emoji的字符需要4个字节才能表示,两个字节的UTF-16
就无能为力了。
Java默认采用了UTF-16
编码,结果导致"🐶".length()
不是返回1,而是2。不得已,Java引入了codePoint这种处理方式,有点无奈。
UCS和CSI
我们计算机的程序会采用两种方式来处理不同的字符集
UCS:(Universal Character Set,泛用字符集)程序输入输出的时候,需要将文本数据变成UCS,统一处理。
CSI:(Character Set Independent 字符集独立),不对文字集做变换,直接处理。
结论
- 积极采用UTF-8。