Unicode和UTF8的区别

又来重复造*了哈哈,网上看了很多帖子,把自己的理解综合一下,保证一篇看懂!

先放点简介 不想看的话可以直接跳到下面对比

Unicode简介

全称为Universal Multiple-Octet Coded Character Set,简称UCS(通用字符集-Universal Character Set)。
历史上存在两个独立的尝试创立单一字符集的组织,即国际标准化组织(ISO)和多语言软件制造商组成的统一码联盟(http://www.unicode.org)。前者开发的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字型。
Unicode的编码方式与ISO 10646的通用字符集(UCS)概念相对应,目前的用于实用的Unicode版本对应于UCS-2,使用16位的编码空间。也就是每个字符占用2个字节,总共可以组合出65535个不同的字符,这大概已经可以覆盖世界上所有文化的符号。实际上目前版本的Unicode尚未填满这16位编码,保留了大量空间作为特殊使用或将来扩展。如果还不够也没有关系,ISO已经准备了UCS-4方案,说简单了就是四个字节来表示一个字符,这样我们就可以组合出21亿个不同的字符出来(最高位有其他用途)。
由ISO制定的ISO 10646(或称ISO/IEC 10646)标准所定义的字符编码方式,采用4字节编码。UCS包含了已知语言的所有字符。除了拉丁语、希腊语、斯拉夫语、希伯来语、阿拉伯语、亚美尼亚语、格鲁吉亚语,还包括中文、日文、韩文这样的象形文字,UCS还包括大量的图形、印刷、数学、科学符号。

  • UCS-2: 与Unicode的2byte编码基本一样;
  • UCS-4: 4byte编码, 目前是在UCS-2前加上2个全0的byte。
    ISO就直接规定必须用两个字节,也就是16位来统一表示所有的字符,对于ASCII里的那些“半角”字符,Unicode保持其原编码不变,只是将其长度由原来的8位扩展为16位,而其他文化和语言的字符则全部重新统一编码。由于“半角”英文符号只需要用到低8位,所以其高8位永远是0,因此这种大气的方案在保存英文文本时会多浪费一倍的空间。
    Unicode 字符集收录了这世界上所有的文字符号和特殊符号。对于每一个符号都定义了一个值,称为代码点(code point)。代码点可以用2个字节表示(UCS-2),也可以用4个字节(UCS-4编码)。
    Unicode在制订时没有考虑与任何一种现有的编码方案保持兼容,这使得GBK与Unicode在汉字的内码编排上完全是不一样的,没有一种简单的算术方法可以把文本内容从Unicode编码和另一种编码进行转换,这种转换必须通过查表来进行。

UTF-8简介

UCS编码虽然定义了每个代码点的编码方式,但是没规定如何传输和存储。比如,在UCS-2码中,英文符号是在ACSII码的前面加上一个0 byte,像“A”的ASCII码 0x41,在UCS码中就是0x0041,这样,对于英文系统来讲会出现大量的0 byte,造成不必要的浪费。而且容易存在对现在的ASCII码不兼容的问题。所以这个重担就落在了UTF编码身上。
于是面向传输的众多UTF(UCS Transfer Format)标准出现了。顾名思义,UTF8就是每次8个位传输数据,而UTF16就是每次16个位,只不过为了传输时的可靠性,从Unicode到UTF时并不是直接的对应,而是要通过一些算法和规则来转换。
(1)UTF-8(Unicode Transformation Format – 8-bit)
UTF-8是一种针对Unicode的可变长度字符编码(定长码),也是一种前缀码。它可以用来表示Unicode标准中的任何字符,且其编码中的第一个字节仍与ASCII兼容,这使得原来处理ASCII字符的软件无需或只需做少部份修改,即可继续使用。因此,它逐渐成为电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。
UTF-8使用一至四个字节为每个字符编码:

  • 128个US-ASCII字符只需一个字节编码(Unicode范围由U+0000至U+007F)。
    *带有附加符号的拉丁文、希腊文、西里尔字母、亚美尼亚语、希伯来文、阿拉伯文、叙利亚文及它拿字母则需要二个字节编码(Unicode范围由U+0080至U+07FF)。
  • 其他基本多文种平面(BMP)中的字符(这包含了大部分常用字)使用三个字节编码。
    *其他极少使用的Unicode辅助平面的字符使用四字节编码。
    在处理经常会用到的ASCII字符方面非常有效。在处理扩展的拉丁字符集方面也不比UTF-16差。对于中文字符来说,比UTF-32要好。同时,UTF-8以字节为编码单元,由位操作的天性使然,使用UTF-8不再存在字节顺序的问题了。一份以utf-8编码的文档在不同的计算机之间是一样的比特流。
    总体来说,在Unicode字符串中不可能由码点数量决定显示它所需要的长度,或者显示字符串之后在文本缓冲区中光标应该放置的位置;组合字符、变宽字体、不可打印字符和从右至左的文字都是其归因。所以尽管在UTF-8字符串中字符数量与码点数量的关系比UTF-32更为复杂,在实际中很少会遇到有不同的情形。
    UTF-8的优点:
    • 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字符串的正则表达式)。
    UTF-8的缺点:
  • 因为每个字符使用不同数量的字节编码,所以寻找串中第N个字符是一个O(N)复杂度的操作,即,串越长,则需要更多的时间来定位特定的字符。同时,还需要位变换来把字符编码成字节,把字节解码成字符。

(2)UTF-16(Unicode Transformation Format – 16-bit)
UTF-16以两个字节为编码单元。在解释一个UTF-16文本前,首先要弄清楚每个编码单元的字节序。Unicode规范中推荐的标记字节顺序的方法是BOM(即字节顺序标记-Byte Order Mark)。在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。Windows就是使用BOM来标记文本文件的编码方式的。
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的编码。

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

常用软件的默认字符集及其查看方法
(1)window下面保存记事本的文本字符集编码为:系统编码GBK;
(2)linux下面的默认字符集编码查看方法:/etc/sysconfig/i18n;
(3)利用cpdetector第三方包可以判断文件或者流的编码;
(4)查询oracle的默认字符集编码方法:select userenv(‘language’) from dual;
(5)早期操作系统的内码是与语言相关的。现在的Windows在内部统一使用Unicode,然后用代码页适应各种语言;
(6)C、C++、Python2内部字符串都是使用当前系统默认编码;
(7)Python3、Java内部字符串用Unicode保存;
(8)Ruby有一个内部变量$KCODE用来表示可识别的多字节字符串的编码,变量值为”EUC” “SJIS” “UTF8″ “NONE”之一。$KCODE的值为”EUC”时,将假定字符串或正则表达式的编码为EUC-JP。同样地,若为”SJIS”时则认定为Shift JIS。若为”UTF8″时则认定为UTF-8。若为”NONE”时,将不会识别多字节字符串。在向该变量赋值时,只有第1个字节起作用,且不区分大小写字母。”e” “E” 代表 “EUC”,”s” “S” 代表 “SJIS”,”u” “U” 代表 “UTF8″,而”n” “N” 则代表 “NONE”。默认值为”NONE”。即默认情况下Ruby把字符串当成单字节序列来处理;
(9)ultraedit在默认情况下是把utf-8转换为unicode编码,你看到的是unicode编码,
如果你想看到真正的utf-8编码,那么在ultraedit中,做如下操作
File—>conversions—>unicode/asc2/utf-8 to utf-8(asc2 editing)

两者区别

先举个例子,比如CSDN的极客日报,

在unicode字符集中是这样的

C	0043
S	0053
D	0044
N	004E
极	6781
客	5BA2
日	65E5
报	62A5

每个字符对应一个16进制的数字,但计算机只懂0101010的二进制,从unicode的简介里我们知道unicode还有UCS-2的格式,转化为

C	 00000000 01000011
S	 00000000 01010011
D	 00000000 01000100 
N	 00000000 01001110
极	01100111 10000001
客	01011011 10100010 
日	01100101 11100101
报	01100010 10100101
0110011110000001 0101101110100010 0110010111100101 0110001010100101
	极				客				日			报

仔细看发现英文字母里前9位全是0!(第9位一般是奇偶校验所以也是0),太浪费硬盘空间了吧!2G的英文字母就有1G的0还行,这谁顶得住,所以就有了UTF出现了,在UTF-8里是这么做的

  • 对于单字节的字符,字节的第一位设为0,对于英文的文本UTF-8只占用1个字节(就把前面8个0全干掉了),和ASCii码就完全一致了
  • 对于n个字节的字符(n>1),第一个字节的前n位全都设为1,第n+1位设为0,后面的字节的前两位都设为10,这n个字节其余空位填充该字符的unicode码,高位用0补足(可能很难理解,直接看例子就行了)

UTF-8的标记位

0xxxxxxx
110xxxxx 10xxxxxx
1110xxxx 10xxxxxx 10xxxxxx
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
... ...

按照规则来的UTF-8下的CSDN极客日报

C	 01000011
S	 01010011
D	 01000100 
N	 01001110
极	11100110 10011110 10000001
客	11100101 10101110 10100010
日	11100110 10010111 10100101
报	11100110 10001010 10100101

图示不同

还不理解?看图!

Unicode和UTF8的区别

对比一下unicode的方案,英文短了,中文多了一个字节

汉字二进制转换器网站

思考

为什么会出现各种编码集?其实是为了建立一个映射关系,一个字符对应一个数字,利用一个数字就可以代表一个字符,因为还是那句话,计算机只能处理二进制码010101,而不是我们看的字符(中文也好英文也好,甚至是各种emoji都是字符),所以我们要对字符进行编码咯

来个小彩蛋,在上面那个二进制转换器里可以将emoji通过utf8转换出来哦,是不是加深了对字符编码集的理解?

输入下面的utf8代码

0xF0 0x9F 0x98 0x82
0xF0 0x9F 0x98 0x85
0xF0 0x9F 0x98 0x80
0xF0 0x9F 0x98 0x86

Unicode和UTF8的区别

总结

总的来说目的就是utf8就是对unicode字符集的再编码,只是为了节省流量和硬盘罢了

参考资料

(1)*-字符编码
(2)《计算机编码知识——区位码、国标码、机内码、输入码、字形码》
(3)《说说字符集和编码》
(4)《编码简介》
(5)《深入了解字符集和编码》
(6)吴秦 《字符集和字符编码(Charset & Encoding)》

上一篇:字符串类似数据库Like匹配


下一篇:C# Unicode 转中文