关于GB2312,GBK,GB18030,Unicode,utf-8,utf-16等的字符集和编码问题一次全说清

关于字符、字符集和字符编码

字符 (Character)是各种文字和符号的总称。包括各国家文字、标点符号、图形符号、数字等,所以不仅只有我们常说的文字,而是抽象的字符。

字符集(Character Set)是一个系统支持的所有抽象字符的集合,字符集种类较多,每个字符集的字符个数也不同。

通过以下代码,可以输出所有支持的规范字符集名称

import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Map;

public class CharacterSetList {

    public static void main(String[] args)
    {
        // Creates a map of charsets
        Map<String, Charset> charsets
                = Charset.availableCharsets();

        // Iterator to store the type
        Iterator<Charset> iterator
                = charsets.values().iterator();
        int i=0;
        // Iterate till we get all the charsets
        while (iterator.hasNext()) {

            // Get the next
            Charset all = (Charset)iterator.next();

            // Displays the name
            //输出20列,左对齐(-号表示左对齐)
            System.out.print(String.format("%-20s", all.displayName()));
            i++;
            if(i%5==0)
                System.out.println();
        }
    }
}

输出结果如下

Big5                Big5-HKSCS          CESU-8              EUC-JP              EUC-KR
GB18030             GB2312              GBK                 IBM-Thai            IBM00858
IBM01140            IBM01141            IBM01142            IBM01143            IBM01144
IBM01145            IBM01146            IBM01147            IBM01148            IBM01149
IBM037              IBM1026             IBM1047             IBM273              IBM277
IBM278              IBM280              IBM284              IBM285              IBM290
IBM297              IBM420              IBM424              IBM437              IBM500
IBM775              IBM850              IBM852              IBM855              IBM857
IBM860              IBM861              IBM862              IBM863              IBM864
IBM865              IBM866              IBM868              IBM869              IBM870
IBM871              IBM918              ISO-2022-CN         ISO-2022-JP         ISO-2022-JP-2
ISO-2022-KR         ISO-8859-1          ISO-8859-13         ISO-8859-15         ISO-8859-16
ISO-8859-2          ISO-8859-3          ISO-8859-4          ISO-8859-5          ISO-8859-6
ISO-8859-7          ISO-8859-8          ISO-8859-9          JIS_X0201           JIS_X0212-1990
KOI8-R              KOI8-U              Shift_JIS           TIS-620             US-ASCII
UTF-16              UTF-16BE            UTF-16LE            UTF-32              UTF-32BE
UTF-32LE            UTF-8               windows-1250        windows-1251        windows-1252
windows-1253        windows-1254        windows-1255        windows-1256        windows-1257
windows-1258        windows-31j         x-Big5-HKSCS-2001   x-Big5-Solaris      x-euc-jp-linux
x-EUC-TW            x-eucJP-Open        x-IBM1006           x-IBM1025           x-IBM1046
x-IBM1097           x-IBM1098           x-IBM1112           x-IBM1122           x-IBM1123
x-IBM1124           x-IBM1129           x-IBM1166           x-IBM1364           x-IBM1381
x-IBM1383           x-IBM29626C         x-IBM300            x-IBM33722          x-IBM737
x-IBM833            x-IBM834            x-IBM856            x-IBM874            x-IBM875
x-IBM921            x-IBM922            x-IBM930            x-IBM933            x-IBM935
x-IBM937            x-IBM939            x-IBM942            x-IBM942C           x-IBM943
x-IBM943C           x-IBM948            x-IBM949            x-IBM949C           x-IBM950
x-IBM964            x-IBM970            x-ISCII91           x-ISO-2022-CN-CNS   x-ISO-2022-CN-GB
x-iso-8859-11       x-JIS0208           x-JISAutoDetect     x-Johab             x-MacArabic
x-MacCentralEurope  x-MacCroatian       x-MacCyrillic       x-MacDingbat        x-MacGreek
x-MacHebrew         x-MacIceland        x-MacRoman          x-MacRomania        x-MacSymbol
x-MacThai           x-MacTurkish        x-MacUkraine        x-MS932_0213        x-MS950-HKSCS
x-MS950-HKSCS-XP    x-mswin-936         x-PCK               x-SJIS_0213         x-UTF-16LE-BOM
X-UTF-32BE-BOM      X-UTF-32LE-BOM      x-windows-50220     x-windows-50221     x-windows-874
x-windows-949       x-windows-950       x-windows-iso2022jp 

我们常见字符集名称

  • ASCII字符集
  • GB2312字符集
  • BIG5字符集
  • GBK字符集
  • GB18030字符集
  • Unicode字符集等

字符编码

计算机中储存的信息都是用二进制数表示的,而我们在屏幕上看到的英文、汉字等字符是二进制数转换之后的结果。通俗的说,按照何种规则将字符存储在计算机中,如’a’用什么表示,称为"编码";反之,将存储在计算机中的二进制数解析显示出来,称为"解码",在解码过程中,如果使用了错误的解码规则,则导致’a’解析成’b’或者乱码。

字符编码就是这样一套法则,使用该法则能够对一个字符集中所有字符与计算机的二进制数进行配对。即在字符集与数字系统之间建立对应关系,因此计算机要准确的处理各种字符集合中的字符,就需要对这些字符进行编码,以便计算机能处理和存储这些字符。

一套字符集必然至少有一套字符编码。

请添加图片描述

字符与字符集和编码

一个字符,首先要在某一种字符集中,然后根据不同的编码方式也会有不同的编码值,以下以“中”字为例。

“中”字,首先,在ASCII字符集中就不包含。在GB2312,GBK,GB18030和Unicode字符集中都有包含。

GB2312,GBK,GB18030的编码有一定的兼容关系。“中”字在三套编码中其编码值都是由两个字节组成,为“0xD6 0xD0"。

但Unicode字符集有utf-32、utf-16和utf-8三种编码方案。”中“字在这些方案中分别的编码为:

utf-16:0x2D 0x4E

utf-8:0xE4 0xB8 0xAD

ASCII字符集

ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于罗马字母表的一套字符集。它主要用于显示现代英语和其他西欧语言。它是最通用的单字节编码系统。

主要包含字符:

  • 控制字符:回车键、退格、换行键等。
  • 可显示字符:英文大小写字符、阿拉伯数字和西文符号。

ASCII编码

使用7位(bits)表示一个字符,共128字符

请添加图片描述

如果愿意,我们也可以通过编写程序来输出ASCII字符集

System.out.println("---------ASCII-----------");
for(int i=0;i<=127;i++){
    System.out.print(i + " "+(char)i + " ");
    if(i%10==0)
        System.out.println();
}

下图是程序输出结果

请添加图片描述

可以看出,ASCII字符集中的控制字符不能被正常的显示出来。

EASCII(ASCII扩展)字符集和编码

7位编码的字符集只能支持128个字符,为了表示更多的欧洲常用字符对ASCII进行了扩展,ASCII扩展字符集使用8位(bits)表示一个字符,共256字符。ASCII扩展字符集:它是从ASCII字符集扩充出来的,扩充后的符号增加了表格符号、计算符号、希腊字母和特殊的拉丁符号。

请添加图片描述

ASCII的最大缺点是只能显示26个拉丁字母、阿拉伯数字和英式标点符号,因此只能用于显示现代美国英语(而且在处理英语当中的外来词如naïve、café、élite等等时,所有重音符号都不得不去掉,即使这样做会违反拼写规则)。而EASCII虽然解决了部份西欧语言的显示问题,但对更多其他语言依然无能为力。因此现在系统多已经抛弃ASCII而转用Unicode。

GB2312字符集和编码

GB2312又称为GB2312-80字符集,全称为《信息交换用汉字编码字符集·基本集》,由原中国国家标准总局发布,1981年5月1日实施。

GB2312是中国国家标准的简体中文字符集。它所收录的汉字已经覆盖99.75%的使用频率,基本满足了汉字的计算机处理需要。在中国大陆和新加坡获广泛使用。

GB2312收录简化汉字及一般符号、序号、数字、拉丁字母、日文假名、希腊字母、俄文字母、汉语拼音符号、汉语注音字母,共 7445 个图形字符。其中小于127的字符的意义与原来相同,但两个大于127的字符连在一起时,就表示一个汉字,前面的一个字节(他称之为高字节)从0xA1用到 0xF7,后面一个字节(低字节)从0xA1到0xFE,共包括6763个汉字,其中一级汉字3755个,二级汉字3008个;包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的682个全角字符。这其中在ASCII里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的"全角"字符,而原来在127号以下的那些就叫"半角"字符了。

请添加图片描述

以下链接可以查看完整的GB2312字符集编码。

GB18030字符集和编码

由于GB 2312-80只收录6763个汉字,有不少汉字,如部分在GB 2312-80推出以后才简化的汉字(如"啰"),部分人名用字,中国台湾及香港使用的繁体字,日语及朝鲜语汉字等,并未有收录在内。于是厂商微软利用GB 2312-80未使用的编码空间,收录GB 13000.1-93全部字符制定了GBK编码。根据微软资料,GBK是对GB2312-80的扩展,也就是CP936字码表 (Code Page 936)的扩展(之前CP936和GB 2312-80一模一样),最早实现于Windows 95简体中文版。虽然GBK收录GB 13000.1-93的全部字符,但编码方式并不相同。GBK自身并非国家标准,只是曾由国家技术监督局标准化司、电子工业部科技与质量监督司公布为"技术规范指导性文件"。原始GB13000一直未被业界采用,后续国家标准GB18030技术上兼容GBK而非GB13000。

GB 18030,全称:国家标准GB 18030-2005《信息技术中文编码字符集》,是中华人民共和国现时最新的内码字集,是GB 18030-2000《信息技术 信息交换用汉字编码字符集 本集的扩充》的修订版。与GB 2312-1980完全兼容,与GBK本兼容,支持GB 13000及Unicode的全部统一汉字,共收录汉字70244个。GB 18030主要有以下特点:

  • 与UTF-8相同,采用多字节编码,每个字可以由1个、2个或4个字节组成。
  • 编码空间庞大,最多可定义161万个字符。
  • 支持中国国内少数民族的文字,不需要动用造字区。
  • 汉字收录范围包含繁体汉字以及日韩汉字

本规格的初版使中华人民共和国信息产业部电子工业标准化研究所起草,由国家质量技术监督局于2000年3月17日发布。现行版本为国家质量监督检验总局和中国国家标准化管理委员会于2005年11月8日发布,2006年5月1日实施。此规格为在中国境内所有软件产品支持的强制规格。

BIG5字符集和编码

Big5,又称为大五码或五大码,是使用繁体中文(正体中文)社区中最常用的电脑汉字字符集标准,共收录13,060个汉字。中文码分为内码及交换码两类,Big5属中文内码,知名的中文交换码有CCCII、CNS11643。Big5虽普及于中国台湾、香港与澳门等繁体中文通行区,但长期只是业界标准。倚天中文系统、Windows等主要系统的字符集都是以Big5为准,但厂商又各自增加不同的造字与造字区,派生成多种不同版本。2003年,Big5被收录到CNS11643中文标准交换码的附录当中,取得了较正式的地位。这个最新版本被称为Big5-2003。Big5码是一套双字节字符集,使用了双八码存储方法,以两个字节来安放一个字。第一个字节称为"高位字节",第二个字节称为"低位字节"。"高位字节"使用了0x81-0xFE,"低位字节"使用了0x40-0x7E,及0xA1-0xFE。

编码字符集
0x8140-0xA0FE保留给用户自定义字符(造字区)
0xA140-0xA3BF标点符号、希腊字母及特殊符号,包括在0xA259-0xA261,安放了九个计量用汉字:兙兛兞兝兡兣嗧瓩糎。
0xA3C0-0xA3FE保留。此区没有开放作造字区用。
0xA440-0xC67E常用汉字,先按笔划再按部首排序。
0xC6A1-0xC8FE保留给用户自定义字符(造字区)
0xC940-0xF9D5次常用汉字,亦是先按笔划再按部首排序。
0xF9D6-0xFEFE保留给用户自定义字符(造字区)

Unicode字符集

当计算机传到世界各个国家时,为了适合当地语言和字符,设计和实现类似GB232/GBK/GB18030/BIG5的编码方案。这样各搞一套,在本地使用没有问题,一旦出现在网络中,由于不兼容,互相访问就出现了乱码现象。

为了解决这个问题,Unicode产生了。Unicode编码系统为表达任意语言的任意字符而设计。它使用4字节的数字来表达每个字母、符号,或者表意文字(ideograph)。每个数字代表唯一的至少在某种语言中使用的符号。(并不是所有的数字都用上了,但是总数已经超过了65535,所以2个字节的数字是不够用的。)被几种语言共用的字符通常使用相同的数字来编码,除非存在一个在理的语源学(etymological)理由使不这样做。不考虑这种情况的话,每个字符对应一个数字,每个数字对应一个字符。即不存在二义性。不再需要记录"模式"了。U+0041总是代表’A’,即使这种语言没有’A’这个字符。

在计算机科学领域中,Unicode(统一码、万国码、单一码、标准万国码)是业界的一种标准,它可以使电脑得以体现世界上数十种文字的系统。Unicode 是通用字符集(Universal Character Set)的标准来发展,并且同时也以书本的形式对外发表。Unicode 还不断在扩增, 每个新版本插入更多新的字符。直至目前为止的第六版,Unicode 就已经包含了超过十万个字符(在2005年,Unicode 的第十万个字符被采纳且认可成为标准之一)、一组可用以作为视觉参考的代码图表、一套编码方法与一组标准字符编码、一套包含了上标字、下标字等字符特性的枚举等。Unicode 组织(The Unicode Consortium)是由一个非营利性的机构所运作,并主导 Unicode 的后续发展,其目标在于:将既有的字符编码方案以Unicode 编码方案来加以取代,特别是既有的方案在多语环境下,皆仅有有限的空间以及不兼容的问题。

通用字符集(Universal Character Set,UCS)是由ISO制定的ISO 10646(或称ISO/IEC 10646)标准所定义的标准字符集。历史上存在两个独立的尝试创立单一字符集的组织,即国际标准化组织(ISO)和多语言软件制造商组成的统一码联盟。前者开发的 ISO/IEC 10646 项目,后者开发的统一码项目。因此最初制定了不同的标准。 1991年前后,两个项目的参与者都认识到,世界不需要两个不兼容的字符集。于是,它们开始合并双方的工作成果,并为创立一个单一编码表而协同工作。从Unicode 2.0开始,Unicode采用了与ISO 10646-1相同的字库和字码;ISO也承诺,ISO 10646将不会替超出U+10FFFF的UCS-4编码赋值,以使得两者保持一致。两个项目仍都存在,并独立地公布各自的标准。但统一码联盟和ISO/IEC JTC1/SC2都同意保持两者标准的码表兼容,并紧密地共同调整任何未来的扩展。在发布的时候,Unicode一般都会采用有关字码最常见的字型,但ISO 10646一般都尽可能采用Century字型。

UTF-32编码

Unicode是字符集,而UTF-32/ UTF-16/ UTF-8是三种字符编码方案。

上述使用4字节的数字来表达每个字母、符号,或者表意文字(ideograph),每个数字代表唯一的至少在某种语言中使用的符号的编码方案,称为UTF-32。UTF-32又称UCS-4,是一种将Unicode字符编码的协定,对每个字符都使用4字节。就空间而言,是非常没有效率的。这种方法有其优点,最重要的一点就是可以在常数时间内定位字符串里的第N个字符,因为第N个字符从第4×Nth个字节开始。虽然每一个码位使用固定长定的字节看似方便,它并不如其它Unicode编码使用得广泛。

”中“字的UTF-32编码为FF FE 2D 4E ,共四个字节

UTF-16

尽管有Unicode字符非常多,但是实际上大多数人不会用到超过前65535个以外的字符。因此,就有了另外一种Unicode编码方式,叫做UTF-16(因为16位 = 2字节)。UTF-16将0–65535范围内的字符编码成2个字节,如果真的需要表达那些很少使用的"星芒层(astral plane)"内超过这65535范围的Unicode字符,则需要使用一些诡异的技巧来实现。

UTF-16编码最明显的优点是它在空间效率上比UTF-32高两倍,因为每个字符只需要2个字节来存储(除去65535范围以外的),而不是UTF-32中的4个字节。并且,如果我们假设某个字符串不包含任何星芒层中的字符,那么我们依然可以在常数时间内找到其中的第N个字符,直到它不成立为止这总是一个不错的推断。其编码方法是:

  • 如果字符编码U小于0x10000,也就是十进制的0到65535之内,则直接使用两字节表示;
  • 如果字符编码U大于0x10000,由于UNICODE编码范围最大为0x10FFFF,从0x10000到0x10FFFF之间 共有0xFFFFF个编码,也就是需要20个bit就可以标示这些编码。用U’表示从0-0xFFFFF之间的值,将其前 10 bit作为高位和16 bit的数值0xD800进行 逻辑or 操作,将后10 bit作为低位和0xDC00做 逻辑or 操作,这样组成的 4个byte就构成了U的编码。

对于UTF-32和UTF-16编码方式还有一些其他不明显的缺点。不同的计算机系统会以不同的顺序保存字节。这意味着字符U+4E2D在UTF-16编码方式下可能被保存为4E 2D或者2D 4E,这取决于该系统使用的是大尾端(big-endian)还是小尾端(little-endian)。(对于UTF-32编码方式,则有更多种可能的字节排列。)只要文档没有离开你的计算机,它还是安全的——同一台电脑上的不同程序使用相同的字节顺序(byte order)。但是当我们需要在系统之间传输这个文档的时候,也许在万维网中,我们就需要一种方法来指示当前我们的字节是怎样存储的。不然的话,接收文档的计算机就无法知道这两个字节4E 2D表达的到底是U+4E2D还是U+2D4E。 为了解决这个问题,多字节的Unicode编码方式定义了一个"字节顺序标记(Byte Order Mark)",它是一个特殊的非打印字符,你可以把它包含在文档的开头来指示你所使用的字节顺序。对于UTF-16,字节顺序标记是U+FEFF。如果收到一个以字节FF FE开头的UTF-16编码的文档,你就能确定它的字节顺序是单向的(one way)的了;如果它以FE FF开头,则可以确定字节顺序反向了。

UTF-8

UTF-8(8-bit Unicode Transformation Format)是一种针对Unicode的可变长度字符编码(定长码),也是一种前缀码。它可以用来表示Unicode标准中的任何字符,且其编码中的第一个字节仍与ASCII兼容,这使得原来处理ASCII字符的软件无须或只须做少部份修改,即可继续使用。因此,它逐渐成为电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。

UTF-8使用一至四个字节为每个字符编码:

  • 128个US-ASCII字符只需一个字节编码(Unicode范围由U+0000至U+007F)。
  • 带有附加符号的拉丁文、希腊文、西里尔字母、亚美尼亚语、希伯来文、阿拉伯文、叙利亚文及它拿字母则需要二个字节编码(Unicode范围由U+0080至U+07FF)。
  • 其他多文种平面(BMP)中的字符(这包含了大部分常用字)使用三个字节编码。例如”中“字的UTF-8编码为三个字节,编码为E4 B8 AD
  • 其他极少使用的Unicode辅助平面的字符使用四字节编码。

在处理经常会用到的ASCII字符方面非常有效。在处理扩展的拉丁字符集方面也不比UTF-16差。对于中文字符来说,比UTF-32要好。同时,由位操作的天性使然,使用UTF-8不再存在字节顺序的问题了。一份以UTF-8编码的文档在不同的计算机之间是一样的比特流。总体来说,在Unicode字符串中不可能由码点数量决定显示它所需要的长度,或者显示字符串之后在文本缓冲区中光标应该放置的位置;组合字符、变宽字体、不可打印字符和从右至左的文字都是其归因。所以尽管在UTF-8字符串中字符数量与码点数量的关系比UTF-32更为复杂,在实际中很少会遇到有不同的情形。

优点

  • UTF-8是ASCII的一个超集。因为一个纯ASCII字符串也是一个合法的UTF-8字符串,所以现存的ASCII文本不需要转换。为传统的扩展ASCII字符集设计的软件通常可以不经修改或很少修改就能与UTF-8一起使用。
  • 使用标准的面向字节的排序例程对UTF-8排序将产生与*于Unicode代码点排序相同的结果。(尽管这只有有限的有用性,因为在任何特定语言或文化下都不太可能有仍可接受的文字排列顺序。)
  • UTF-8和UTF-16都是可扩展标记语言文档的标准编码。所有其它编码都必须通过显式或文本声明来指定。
  • 任何面向字节的字符串搜索算法都可以用于UTF-8的数据(只要输入仅由完整的UTF-8字符组成)。但是,对于包含字符记数的正则表达式或其它结构必须小心。
  • UTF-8字符串可以由一个简单的算法可靠地识别出来。就是,一个字符串在任何其它编码中表现为合法的UTF-8的可能性很低,并随字符串长度增长而减小。举例说,字符值C0,C1,F5至FF从来没有出现。为了更好的可靠性,可以使用正则表达式来统计非法过长和替代值(可以查看W3 FAQ: Multilingual Forms上的验证UTF-8字符串的正则表达式)。

缺点

因为每个字符使用不同数量的字节编码,所以寻找串中第N个字符是一个O(N)复杂度的操作 , 即,串越长,则需要更多的时间来定位特定的字符。同时,还需要位变换来把字符编码成字节,把字节解码成字符。

Accept-Charset/Accept-Encoding/Accept-Language/Content-Type/Content-Encoding/Content-Language

在HTTP中,与字符集和字符编码相关的消息头是Accept-Charset/Content-Type,另外主区区分Accept-Charset/Accept-Encoding/Accept-Language/Content-Type/Content-Encoding/Content-Language:

  • Accept-Charset:浏览器申明自己接收的字符集,这就是本文前面介绍的各种字符集和字符编码,如gb2312,utf-8(通常我们说Charset包括了相应的字符编码方案);
  • Accept-Encoding:浏览器申明自己接收的编码方法,通常指定压缩方法,是否支持压缩,支持什么压缩方法(gzip,deflate),(注意:这不是指字符编码);
  • Accept-Language:浏览器申明自己接收的语言。语言跟字符集的区别:中文是语言,中文有多种字符集,比如big5,gb2312,gbk等等;
  • Content-Type:WEB服务器告诉浏览器自己响应的对象的类型和字符集。例如:Content-Type: text/html; charset=‘gb2312’
  • Content-Encoding:WEB服务器表明自己使用了什么压缩方法(gzip,deflate)压缩响应中的对象。例如:Content-Encoding:gzip
  • Content-Language:WEB服务器告诉浏览器自己响应的对象的语言。

Windows记事本的编码

使用Windows记事本的“另存为”,可以在GBK、Unicode、Unicode big endian和UTF-8这几种编码方式间相互转换。同样是txt文件,Windows是怎样识别编码方式的呢?

请添加图片描述

不同的国家和地区制定了不同的标准,由此产生了 GB2312、GBK、GB18030、Big5、Shift_JIS 等各自的编码标准。这些使用多个字节来代表一个字符的各种汉字延伸编码方式,称为 ANSI 编码。在简体中文Windows操作系统中,ANSI 编码代表 GBK编码;在繁体中文Windows操作系统中,ANSI编码代表Big5;在日文Windows操作系统中,ANSI 编码代表 JIS 编码。

Unicode、UCS和UTF

前面提到从ASCII、GB2312、GBK到GB18030的编码方法是向下兼容的。而Unicode只与ASCII兼容(更准确地说,是与ISO-8859-1兼容),与GB码不兼容。例如“汉”字的Unicode编码是6C49,而GB码是BABA。

Unicode的学名是"Universal Multiple-Octet Coded Character Set",简称为UCS。UCS可以看作是"Unicode Character Set"的缩写。

根据维基百科全书的记载:历史上存在两个试图独立设计Unicode的组织,即国际标准化组织(ISO)和一个软件制造商的协会(unicode.org)。ISO开发了ISO 10646项目,Unicode协会开发了Unicode项目。

在1991年前后,双方都认识到世界不需要两个不兼容的字符集。于是它们开始合并双方的工作成果,并为创立一个单一编码表而协同工作。从Unicode2.0开始,Unicode项目采用了与ISO 10646-1相同的字库和字码。

目前两个项目仍都存在,并独立地公布各自的标准。Unicode协会现在的最新版本是2005年的Unicode 4.1.0。ISO的最新标准是10646-3:2003。

UCS规定了怎么用多个字节表示各种文字。怎样传输这些编码,是由UTF(UCS Transformation Format)规范规定的,常见的UTF规范包括UTF-8、UTF-7、UTF-16。

IETF 的RFC2781和RFC3629描述了UTF-16和UTF-8的编码方法。

UCS-2、UCS-4、BMP

UCS有两种格式:UCS-2和UCS-4。顾名思义,UCS-2就是用两个字节编码,UCS-4就是用4个字节(实际上只用了31位,最高位必须为0)编码。

UCS-2有2的16次方=65536个码位,UCS-4有2的31次方=2147483648个码位。

UCS -4根据最高位为0的最高字节分成2^7=128个group。每个group再根据次高字节分为256个plane。每个plane根据第3个字节分为 256行 (rows),每行包含256个cells。当然同一行的cells只是最后一个字节不同,其余都相同。

请添加图片描述

group 0的plane 0被称作Basic Multilingual Plane, 即BMP。或者说UCS-4中,高两个字节为0的码位被称作BMP。

将UCS-4的BMP去掉前面的两个零字节就得到了UCS-2。在UCS-2的两个字节前加上两个零字节,就得到了UCS-4的BMP。而目前的UCS-4规范中还没有任何字符被分配在BMP之外。

UTF编码

UTF-8就是以8位为单元对UCS进行编码。从UCS-2到UTF-8的编码方式如下:

UCS-2编码(16进制) UTF-8 字节流(二进制)
0000 - 007F 0xxxxxxx
0080 - 07FF 110xxxxx 10xxxxxx
0800 - FFFF 1110xxxx 10xxxxxx 10xxxxxx

例 如“汉”字的Unicode编码是6C49。6C49在0800-FFFF之间,所以肯定要用3字节模板了:1110xxxx 10xxxxxx 10xxxxxx。将6C49写成二进制是:0110 110001 001001, 用这个比特流依次代替模板中的x,得到:11100110 10110001 10001001,即E6 B1 89。

请添加图片描述

UTF- 16以16位为单元对UCS进行编码。对于小于0x10000的UCS码,UTF-16编码就等于UCS码对应的16位无符号整数。对于不小于 0x10000的UCS码,定义了一个算法。不过由于实际使用的UCS2,或者UCS4的BMP必然小于0x10000,所以就目前而言,可以认为UTF -16和UCS-2基本相同。但UCS-2只是一个编码方案,UTF-16却要用于实际的传输,所以就不得不考虑字节序的问题。所以,就有Unicode编码和Unicode big endian编码的区另,big endian和little endian称作“大尾”和“小尾”。

Unicode(little endian)和Unicode big endian

big endian和little endian是CPU处理多字节数的不同方式。例如“汉”字的Unicode编码是6C49。那么写到文件里时,究竟是将6C写在前面,还是将49写在前 面?如果将6C写在前面,就是big endian。还是将49写在前面,就是little endian。

“endian”这个词出自《格列佛游记》。小人国的内战就源于吃鸡蛋时是究竟从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开,由此曾发生过六次叛乱,其中一个皇帝送了命,另一个丢了王位。

我们一般将endian翻译成“字节序”,将big endian和little endian称作“大尾”和“小尾”。

UTF的字节序和BOM

UTF -8以字节为编码单元,没有字节序的问题。UTF-16以两个字节为编码单元,在解释一个UTF-16文本前,首先要弄清楚每个编码单元的字节序。例如收 到一个“奎”的Unicode编码是594E,“乙”的Unicode编码是4E59。如果我们收到UTF-16字节流“594E”,那么这是“奎”还是 “乙”?

Unicode规范中推荐的标记字节顺序的方法是BOM。BOM不是“Bill Of Material”的BOM表,而是Byte Order Mark。BOM是一个有点小聪明的想法:

在UCS 编码中有一个叫做"ZERO WIDTH NO-BREAK SPACE"的字符,它的编码是FEFF。而FFFE在UCS中是不存在的字符,所以不应该出现在实际传输中。UCS规范建议我们在传输字节流前,先传输 字符"ZERO WIDTH NO-BREAK SPACE"。

这样如果接收者收到FEFF,就表明这个字节流是Big-Endian的;

请添加图片描述

如果收到FFFE,就表明这个字节流是Little-Endian的。因此字符"ZERO WIDTH NO-BREAK SPACE"又被称作BOM。

请添加图片描述

UTF -8不需要BOM来表明字节顺序,但可以用BOM来表明编码方式。字符"ZERO WIDTH NO-BREAK SPACE"的UTF-8编码是EF BB BF。所以如果接收者收到以EF BB BF开头的字节流,就知道这是UTF-8编码了。

Windows就是使用BOM来标记文本文件的编码方式的。

请添加图片描述

主要字符集编码比较

下面通过java代码。对同一个字符串,比较不同的字符集的编码。从而对上面的介绍进行一个总结

import java.io.UnsupportedEncodingException;

public class Test {
    public void toHex(byte[] bytes){
        for(byte b :bytes){
            System.out.printf("%x",b);
            System.out.print(" ");
        }
        System.out.println("");
    }
    public void test() throws UnsupportedEncodingException {
        String hello = "Hello 中国";
        byte[] bytes;

        System.out.println(hello);

        System.out.println("US-ASCII");
        bytes = hello.getBytes("US-ASCII");
        toHex(bytes);
        System.out.println("ISO-8859-1");
        bytes = hello.getBytes("ISO-8859-1");
        toHex(bytes);
        System.out.println("GB2312");
        bytes = hello.getBytes("GB2312");
        toHex(bytes);
        System.out.println("GBK");
        bytes = hello.getBytes("GBK");
        toHex(bytes);
        System.out.println("GB18030");
        bytes = hello.getBytes("GB18030");
        toHex(bytes);
        System.out.println("UTF-8");
        bytes = hello.getBytes("UTF-8");
        toHex(bytes);
        System.out.println("UTF-16BE");
        bytes = hello.getBytes("UTF-16BE");
        toHex(bytes);
        System.out.println("UTF-16LE");
        bytes = hello.getBytes("UTF-16LE");
        toHex(bytes);
        System.out.println("UTF-16");
        bytes = hello.getBytes("UTF-16");
        toHex(bytes);
        System.out.println("UTF-32");
        bytes = hello.getBytes("UTF-32");
        toHex(bytes);
    }

    public static void main(String[] args) throws UnsupportedEncodingException {
        new Test().test();
    }
}

运行结果如下:

Hello 中国
US-ASCII
48 65 6c 6c 6f 20 3f 3f 
ISO-8859-1
48 65 6c 6c 6f 20 3f 3f 
GB2312
48 65 6c 6c 6f 20 d6 d0 b9 fa 
GBK
48 65 6c 6c 6f 20 d6 d0 b9 fa 
GB18030
48 65 6c 6c 6f 20 d6 d0 b9 fa 
UTF-8
48 65 6c 6c 6f 20 e4 b8 ad e5 9b bd 
UTF-16BE
0 48 0 65 0 6c 0 6c 0 6f 0 20 4e 2d 56 fd 
UTF-16LE
48 0 65 0 6c 0 6c 0 6f 0 20 0 2d 4e fd 56 
UTF-16
fe ff 0 48 0 65 0 6c 0 6c 0 6f 0 20 4e 2d 56 fd 
UTF-32
0 0 0 48 0 0 0 65 0 0 0 6c 0 0 0 6c 0 0 0 6f 0 0 0 20 0 0 4e 2d 0 0 56 fd 
  • 结果分析
    字符串为:Hello 中国

US-ASCII为单字节编码,前面的“48 65 6c 6c 6f 20 ”为“Hello ”,其中20是空格。字符集中不包括中文字符,遇到中文字符,会转化为3f,也就是?。
48 65 6c 6c 6f 20 3f 3f

ISO-8859-1为单字节编码,兼容ASCII编码,字符集中也不包括中文字符,遇到中文字符,会转化为3f,也就是?。
48 65 6c 6c 6f 20 3f 3f

GB2312兼容ASCII编码,单字节小于等于127的,则为ASCII字符,单字节高于127的,则两个字节表示一个字符。所以,前面的“48 65 6c 6c 6f 20”与前面两种编码一样。后面两个字节为一个字符,即“d6 d0”为“中”字,“b9 fa”为“国”字。
48 65 6c 6c 6f 20 d6 d0 b9 fa

GBK兼容GB2312
48 65 6c 6c 6f 20 d6 d0 b9 fa

GB18030兼容GBK
48 65 6c 6c 6f 20 d6 d0 b9 fa

UTF-8变长编码,兼容ASCII编码,因此英文字符的编码与ASCII编码一致,中文字符使用三个字节编码,即“e4 b8 ad”为“中”,“e5 9b bd”为“国”字符的编码
48 65 6c 6c 6f 20 e4 b8 ad e5 9b bd

UTF-16BE为双字节编码,16BE,表示是Big Endian,是代表两个字节谁在前和谁在后的顺序。与下面的LE(Little Endian)的字节顺序相反。虽然英文字符与ASCII编码的相同,但是占两个字节。中文字符也是占两个字节。
0 48 0 65 0 6c 0 6c 0 6f 0 20 4e 2d 56 fd

UTF-16LE为双字节编码,与UTF-16BE编码两个字节的顺序相反。
48 0 65 0 6c 0 6c 0 6f 0 20 0 2d 4e fd 56

UTF-16双字节,并带有BOM(Byte Order Mark)信息,fe ff表示后面的编码的字节是采用BE方式,如果是ff fe开头,则后面的是采用LE方式。
fe ff 0 48 0 65 0 6c 0 6c 0 6f 0 20 4e 2d 56 fd

UTF-32为四字节固定编码,可以看出,包括英文字符,也是采用4个字节进行编码。
0 0 0 48 0 0 0 65 0 0 0 6c 0 0 0 6c 0 0 0 6f 0 0 0 20 0 0 4e 2d 0 0 56 fd

参考资料

GBK、Unicode、Unicode big endian和UTF-8这几种编码方式间相互转换: https://blog.csdn.net/whmwg/article/details/84575664
字符集和字符编码(Charset & Encoding): https://www.cnblogs.com/skynet/archive/2011/05/03/2035105.html
Java Charset availableCharsets()用法及代码示例: https://vimsky.com/examples/usage/charset-availablecharsets-method-in-java-with-examples.html
史上最骚最全最详细的IO流教程,小白都能看懂!: https://blog.csdn.net/qq_44543508/article/details/102831084

  • 2
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java Man

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值