浅析charset与encoding

基本概念

计算机科学中的 charset 术语可参考RFC2278,有三个重要的术语:

  1. charset(字符集) 定义为将字节序列转换为字符序列的方法,是编码字符集与字符编码方案的结合。
  2. coded character sets(编码字符集) 是抽象字符集合和整数集合之间的映射关系,如US-ASCII,ISO8859-1,Unicode。
  3. character-encoding scheme(字符编码方案) 是编码字符集和字节序列集合之间的映射关系,如UTF-8,UTF-16,EUC(可以编码东亚文字:简体中文,繁体中文,日文,韩文)。

在单字节时代(ASII,ISO-8859),并没有字符编码方案的概念。只有在多字节时代,为了兼容US-ASCII以及方便存储和传输等,才出现了字符编码方案。可以理解为编码字符集最初将整数映射作为“默认编码方案”。

下面一一分析一些常见的名称:

ASCII

现代计算机发明于美国,最开始只需要将英文字符转换为二进制数据,ASCII诞生。ASCII包含了英文字母、数字、半角符号、控制符。

ASCII又称为US-ASCII,使用定长的1个字节记录抽象字符映射的整数,实际使用了字节的7位进行编码,包含128个抽象字符,最高位用作奇偶校验位。

此外存在扩展ASCII,区别在于使用了最高位来表示字符,但普遍使用以及其它字符编码方案兼容的是US-ASCII,当使用ASCII术语时并不包含扩展ASCII部分。

ASCII是一种编码字符集。

ISO-8859

其它采用字母文字的语言,如法语、德语,它们的文字也需要编码为二进制。由于它们的字符集合不大,因此单字节剩余的容量足以包含。

ISO-8859是一系列标准,定义了15个采用8位整数的字符集,低位兼容ASCII,高位用于其它字母文字。不同的子标准,相同的高位表示不同的字符,因此它们是互不兼容的。

对开发者而言,其中最常见的是ISO 8859-1,又称作latin1,其包含了西欧语言的大部分字符。

ISO-8859是一系列编码字符集。

GB2312,GBK

计算机传入中国后,汉字也需要编码为二进制数据。以汉字为代表的象形文字其抽象字符集合远远超出单字节所能容纳的大小。GB2312-80是中国官方发布的编码字符集标准,收录了汉字、拉丁字母、希腊字母、俄文字母、日文假名、符号等7445个图形字符。

GB2312字符集分成94个区,每区有94个位,共8836个码位。采用两个字节编码,分别记录区号和位号,如:"啊"字位于16区1位,则是16-1。

GB2312标准是一种编码字符集。

但工程事实是必须兼容ASCII,普遍采用的是EUC-CN表示法:一个变长的编码方案,对ASCII字符采用原来的单字节;对于GB2312字符,区号和位号都加上0xA0(160)。这样一个小于128的字节还是表示ASCII字符,而大于128的则一定表示的是GB2312的字符。因为GB2312收录了ASCII中已有的字符,存在全角、半角的区分(具体原因与印刷业有关)。

EUC-CN是一种字符编码方案,相比UTF-8、UTF-16显然默默无闻。

通常所指的GB2312就是EUC-CN表示法与GB2312编码字符集的组合。

没有被收录的汉字还有许多,因此中国官方又发布了GBK(汉字内码扩展规范)。GBK扩展了GB2312所包含的字符,达到21886个字符。

GBK默认采用EUC-CN表示法,完全兼容采用EUC-CN表达法的GB2312。区的数量,每个区的位数都进行了扩展。第二个字节不再一定大于128,只有通过第一个字节判断编码的是ASCII字符还是GBK字符。

通常所指的GBK就是EUC-CN表示法与GBK编码字符集的组合。

Unicode,UTF-8,UTF-16,UTF-32

当计算机传入世界各国家或地区后,它们都根据自己的语言定义了字符集。传统编码如GB2312,ISO8859-1,big5等,它们支持英语(兼容ASCII)/本地语言的环境,却无法支持多语言环境,因此Unicode标准诞生。Unicode对现有的大部分抽象字符集合进行了整理、编码,从而实现多语言环境。

Unicode标准是由Unicode联盟制定的,后来由国际标准化组织(ISO)进行标准化为ISO/IEC 10646系列标准,它们拥有相同的字符集和编码方案。

Unicode的编码空间为U+0000到U+10FFFF,共有1,112,064个码位(code point)。共划分为17个平面,其中U+0000到U+FFFF称为基本多语言平面(BMP),其余称为辅助平面

Unicode标准定义了编码字符集the Universal Coded Character Set (UCS)。

UCS最开始的版本是USC-2(2-byte Universal Character Set)。采用两个字节表示码位,只能编码BMP对应的字符。其兼容标准ASCII(高位为0)。

但随着Unicode的扩展,辅助平面的添加,两个字节已经不足以表示所有码位。Unicode编码字符集的下一个版本是UCS-4(4-byte Universal Character Set),使用4个字节表示码位,兼容`UCS-2(高位两字节为0)。

采用2字节或4字节的USC相比ASCII的1个字节浪费存储空间,而且影响传输效率。因此Unicode定义了the Unicode Transformation Format (UTF) encodings实现针对Unicode的可变长度的编码方案,来解决上述问题。

UTF-8使用1~4个字节对UCS编码,其中:

  • ASCII字符采用1个字节表示,并兼容ASCII
  • 大部分汉字采用3个字节表示

UTF-8保证了一个字符的字节序列不会包含在另一个字符的字节序列中。

UTF-16是UCS-2的超集,定义它并非为了传输和存储,而是对UCS-2扩展以支持非标准平面的字符。使用2或4个字节对UCS编码,2个字节的基本单元称为码元。对于BMP对应的字符与UCS-2相同,一个码元数值上与码位相等。对于辅助字符采用两个码元来表示,称为前导代理和后导代理,它们将码位进行编码,保证了都不与BMP中有效字符的码位冲突。

UTF-32功能上与UCS-4相同。

UTF中的BE,LE与BOM

大端(BE)模式小端(LE)模式指的是的字节序。在计算机底层中指数据在内存中的字节序,大端指数据的高字节存放在内存的低地址中,数据的低字节存放在内存的高地址中,而小端模式相反。

这里的BE,LE指的是编码单元在存储和传输过程中的字节序。UTF-16与UTF-32都存在大小端区分:

NameUTF-8UTF-16UTF-16BEUTF-16LEUTF-32UTF-32BEUTF-32LE
Code unit size8 bits16 bits16 bits16 bits32 bits32 bits32 bits
Byte orderN/A<BOM>big-endianlittle-endian<BOM>big-endianlittle-endian

UTF-8的编码单元为一个字节,因此不需要提供大小端模式。而UTF-16和UTF-32的编码单元分别为2个字节和4个字节,因此提供了3种风格,<BOM>指由BOM决定字节序,如果未使用BOM,默认大端模式。

BOM(byte order mark)是一个Unicode字符,UTF-8,UTF-16,UTF-32都可以使用BOM。其作为魔数(magic number)表明其所在文本:

  • 声明采用的哪种字节序
  • 显式声明该文本采用的编码字符集为Unicode
  • 声明其具体采用的哪种Unicode编码方案

大小端和BOM的使用场景不多,主要是Windows平台采用。

结合具体环境理解

通过以上概念的学习总结后,回头看一些具体环境的charset与encoding,就很容易想明白了。

Java(<=8)

char为2个字节大小,用来表示字符,因此Java的char只支持UCS-2中的字符。由于日常使用的汉字、英文、符号都位于基本面上,可以通过char表示,这种限制很少影响编程使用。但特殊情况,如许多Emoji字符将无法通过char来表示。

char frowningFace='☹';//U+2639
char slightlyFrowningFace='\uD83D\uDE41';//U+1F641 辅助平面字符?无法用char表示

String表示字符串,使用UTF-16编码字符串并存储在底层的char[]中。我们在编程时一般将底层char[]的每个元素当作一个字符,进行比较或是匹配:

if(str.charAt(0)=='<')
    ...

因为UTF-16的基本面字符码元与辅助字符码元不冲突的性质,这种比较结果上并没有错误。只是注意得到的char不一定表示一个字符,而可能只是字符的部分。
更好的方式,是以码位的方式进行遍历,但char只支持基本面的码位,因此实际上并不怎么方便:

for(int i=0;i<faces.codePointCount(0,faces.length());i++){
    System.out.printf("%x\n",faces.codePointAt(i));
}

Go

Go提供了字符类型rune,rune占4个字节,可以完整的表示所有的Unicode码位,因此rune完美的支持了UCS-4

frowningFace,slightlyFrowningFace:='☹','?'//相比Java,Go的rune能表示所有Unicode字符

string为字符串类型,字符串通过UTF-8编码保存在底层的byte[]中。Go支持for-range对字符串中的Unicode字符进行迭代:

for pos, char := range str {
    ...
}

可以看到,作为更新的编程语言,Go对于字符编码的支持足够现代化。

Windows(<=Win10 1607,更新版本未测试)

这里只讨论Windows自带的文本编辑器。notepad支持的编码方案包括:
726809-20181217220916510-807344640.png

每个encoding的实际情况为为:

  • ASCII,统称所有的本地charset,如gb2312、big5等,与系统语言环境有关。
  • Unicode,实际为带有BOM的UCS-2 LE。
  • Unicode big endian,实际为带有BOM的UCS-2 BE。
  • UTF-8,实际为带有BOM的UTF-8。

notepad对于编码的命名比较混乱,实际一般也很少使用BOM,因此在Windows环境下最好不要使用notepad创建和编辑文本文档,而推荐使用其它软件,如Notepad++就很好的支持了不同的字符集和编码。

总结

理解charset的关键在于明白编码字符集(coded character sets)和编码方案(character-encoding scheme)的区别。并对常用的US-ASCII,ISO8859-1,GB2312,GBK,UTF-8,UTF-16等有一个基本的概念,了解它们的发展和特点。

在使用US-ASCII,ISO8859-1,GB2312,GBK时我们不需要考虑编码方案的问题,因为它们的编码方式事实唯一的,只有在Unicode时才需要考虑编码方案的问题。

不精确的讲,我们可以如下称呼:

  • 字符集(charset):US-ASCII,ISO8859-1,GB2312,GBK,Unicode
  • 编码/编码方案(encoding):UTF-8,UTF-16

参考
Java API: Class Charset
RFC2278 http://www.faqs.org/rfcs/rfc2278.html
ASCII https://en.wikipedia.org/wiki/ASCII
ISO 8859 https://en.wikipedia.org/wiki/ISO%2FIEC_8859
GB2312 https://en.wikipedia.org/wiki/GB_2312
Extended Unix Code https://en.wikipedia.org/wiki/Extended_Unix_Code
GBK https://en.wikipedia.org/wiki/GBK
汉字内码扩展规范编码表 http://www.yywzw.com/show.aspx?id=1159
Unicode https://en.wikipedia.org/wiki/Unicode
Universal_Coded_Character_Set https://en.wikipedia.org/wiki/Universal_Coded_Character_Set
UTF_BOM http://www.unicode.org/faq/utf_bom.html
中文输入法为什么会有全角和半角的区别? https://www.zhihu.com/question/19605819

转载于:https://www.cnblogs.com/redreampt/p/7903305.html

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值