关于字符编码:ascii、unicode与utf-8
转自:https://foofish.net/unicode_utf-8.html
阮一峰老师对普及计算机基础技术功不可没,但毕竟老师不是神,因此也避免不了对某些概念有一些错误的理解,《字符编码笔记:ASCII,Unicode 和 UTF-8 》 是阮老师10年前写的一篇关于字符编码的科普文章,现在用 Google 搜关键字该文章依然名列前茅,可见他的文章有多大影响力,不过这是后话,但里面的内容是否正确是值得商榷的事。
话说天下大势,分久必合,合久必分,在字符编码世界里也遵循这样一种历史规律。从 ASCII 码到 EASCII(ISO8859),发展到后来的群雄并起,各国家地区拥有自己的编码格式,中国有 GBK,日本有 JIS,台湾有 BIG5,然而这是一个混战的年代,大家各自为政,没有统一的编码标准,交流起来极其麻烦。字符编码成为了程序员最头疼的问题之一
于是在 1991 年,国际标准化组织和统一码联盟组织各自开发了 ISO/IEC 10646(USC)和 Unicode 项目。他们两的野心都不小,一统江湖,千秋万载。不过很快双方都意识到世界上并不需要两个不兼容的字符集。于是他们就编码问题进行了一次非常友好地会晤,决定彼此把工作内容合并,项目还是独立存在,各自发布各自的标准,前提是两者必须保持兼容。不过由于 Unicode 这一名字比较好记,因而它使用更为广泛,成为了事实上的统一编码标准。
以上是对字符编码历史的一个简要回顾,Unicode 究竟是什么东西,它是一种编码格式吗?中文维基百科对 Unicode 的解释也是让人一头雾水,摸不着头脑。那么来看看阮老师怎么说:
可以想象,如果有一种编码,将世界上所有的符号都纳入其中。每一个符号都给予一个独一无二的编码,那么乱码问题就会消失。这就是Unicode,就像它的名字都表示的,这是一种所有符号的编码。
这句话读起来很拗口,有三个地方出现了「编码」二字。不知阮老师对「编码」的理解是什么?但可以肯定的是这三个「编码」在这句话里面不是同一个意思。因此有必要先解释一下「编码」到底是什么意思。
「编码」在汉语里可以作动词使用,编码就是把一个字符(严格一点说是字符在字符集中的编号 code point)转换成一个字节序列,以便在网络传输或者存储到文本中。比如「好」在 Unicode 中的编号是 U+597d,经过 UTF-8 编码后会转换成二进制序列是 '\xe5\xa5\xbd' 。
>>> a = u"好"
>>> a
u'\u597d'
>>> b = a.encode("utf-8")
>>> b
'\xe5\xa5\xbd'
>>>
「编码」还可以做名词使用,作为名词使用时,就是指一种具体的编码实现方式,比如 ASCII 编码,GBK 编码,UTF-8 编码,都叫做编码,是一种具体的实实在在的编码格式。
把编码概念弄清楚了之后,我们就可以来定义 Unicode 了。其实 Unicode 是一个囊括了世界上所有字符的字符集,其中每一个字符都对应有唯一的编码值(code point),然而它并不是一种什么编码格式,仅仅是字符集而已。 Unicode 字符要存储要传输怎么办,它不管,具体怎么编码,你们可以自己去实现,可以用 UTF-8、UTF-16、甚至用 GBK 来编码也是可以的。比如:
>>> a = u"好"
>>> a
u'\u597d'
>>> b = a.encode("gbk")
>>> b
'\xba\xc3'
「好」用 GBK 编码转后就是用两个字节表示,用 UTF-8 编码就是 3 个字节,同一个字符用不同的编码方式占用的字节长度不一。
阮老师谈到 Unicode 的问题 时说:
这里就有两个严重的问题,第一个问题是,如何才能区别 Unicode 和 ASCII ?
把 Unicode 和 ASCII 码作为字符集来理解时,需要区分二者吗?不需要,因为 Unicode 是在 ASCII 的基础之上建立的,比如 ASCII 码字符集中的 「A」对应的 code ponit 是 0x41,Unicode 码是 U+0041,两者是一样的。至于中日韩文字用 ASCII 根本没法表示,所以也不存在混淆的说法。他又接着说:
计算机怎么知道三个字节表示一个符号,而不是分别表示三个符号呢?
计算机怎么不知道是用几个字节表示呢?你把字符存储到文本的时候肯定是要指定一种编码格式的,既然指定了编码格式,那么读取的时候也按照该种编码格式反过来读就行了。老师把这也当做一个问题就有点牵强附会了。
再来看阮老师说 Unicode 的第二个问题:
第二个问题是,我们已经知道,英文字母只用一个字节表示就够了,如果Unicode统一规定,每个符号用三个或四个字节表示,那么每个英文字母前都必然有二到三个字节是0,这对于存储来说是极大的浪费,文本文件的大小会因此大出二三倍,这是无法接受的。
Unicode 并没有统一规定每个符号用三个或者四个字节表示。Unicode 只规定了每个字符对应到唯一的代码值(code point),代码值 从 0000 ~ 10FFFF 共 1114112 个值 ,真正存储的时候需要多少个字节是由具体的编码格式决定的。比如:字符 「A」用 UTF-8 的格式编码来存储就只占用1个字节,用 UTF-16 就占用2个字节,而用 UTF-32 存储就占用4个字节。
再看来看这张图:
阮老师对 Unicode 编码的解释是:
Unicode编码指的是UCS-2编码方式,即直接用两个字节存入字符的Unicode码。这个选项用的little endian格式。
阮老师全文并没有对 UCS-2 做任何解释,导致很多读者非常疑惑,因此,我有必要在这里解释一下。
UTF( Unicode Transformation Format)编码 和 USC(Universal Coded Character Set) 编码分别是 Unicode 、ISO/IEC 10646 编码体系里面两种编码方式,UCS 分为 UCS-2 和 UCS-4,而 UTF 常见的种类有 UTF-8、UTF-16、UTF-32。因为两种字符集是相互兼容的,所以这几种具体的编码格式也有着对应的等值关系。
UCS-2 是使用两个定长的字节来表示一个字符,UTF-16 也是使用两个字节,不过 UTF-16 是变长的(网上很多错误的说法说 UTF-16是定长的),遇到两个字节没法表示时,会用4个字节来表示,因此 UTF-16 可以看作是在 UCS-2 的基础上扩展而来的。而 UTF-32 与 USC-4 是完全等价的。
再回到这张图片,之所以在 Windows下有 Unicode 编码这样一种说法,其实是 Windows 的一种错误表示方法,或许是因为历史原因还是其他问题一直沿用至今。这因为如此,导致绝大多数初学者误以为 Unicode 就是一种编码格式,所以 Windows 有时也是误人子弟啊。反正你就理解为 UTF-16 编码就是。
阮老师对什么是大端和小端的解释显得很唐突。为什么会有大端小端?也解释的含糊其词:
上一节已经提到,Unicode码可以采用UCS-2格式直接存储。以汉字"严"为例,Unicode码是4E25,需要用两个字节存储,一个字节是4E,另一个字节是25。存储的时候,4E在前,25在后,就是Big endian方式;25在前,4E在后,就是Little endian方式。因此,第一个字节在前,就是"大头方式"(Big endian),第二个字节在前就是"小头方式"(Little endian)。
小端就是低位字节放在内存的低地址端,高位字节放在内存的高地址端。与之相反,大端就是高位字节放在内存的低地址端,低位字节放在内存的高地址端。举例来说,字节序列 '0x12 34 56 78' 在内存中的表示形式为:
# 小端模式
低地址 ------------------> 高地址
0x78 | 0x56 | 0x34 | 0x12 # 大端模式
低地址 -----------------> 高地址
0x12 | 0x34 | 0x56 | 0x78
至于为什么会有大端和小端之分呢?对于 16 位或者 32 位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节排放的问题,因为不同机器类型读取双字节的时候方式不一样,因此就导致了大端存储模式和小端存储模式的存在,两者并没有孰优孰劣。
前面我已经解释过了 UCS-2 可以理解为 UTF-16 编码,汉字「严」的 Unicode 编号是 U+4E25,存储的时候到底需要几个字节,跟具体的编码格式有关,如果是用 UTF-8 编码,结果为 'e4 b8 a5',需要 3 个字节来存储。用 UTF-16 编码,结果为 '4e 25',用两个字节存储,UTF-32 就要用4个字节了。因为 UTF-16 和 UTF-32 都是以双字节为单位来储存字符的,双字节就存在大小端的问题了。而 UTF-8 的编码单元是1个字节,所以就不用考虑字节序问题。
他又说:
Unicode规范中定义,每一个文件的最前面分别加入一个表示编码顺序的字符,这个字符的名字叫做"零宽度非换行空格"(ZERO WIDTH NO-BREAK SPACE),用FEFF表示。这正好是两个字节,而且FF比FE大1。
如果一个文本文件的头两个字节是FE FF,就表示该文件采用大头方式;如果头两个字节是FF FE,就表示该文件采用小头方式
这里就错得有点离谱了。在 UTF-16 中,FEFF 是字节顺序标记(byte order mark,BOM),用来标记编码后的字节序列高位在前还是低位在前。小端低位在前,高位在后,用 「FFFE」 标识。大端高位在前,低位在后,用 「FEFF」 标识。
上图就是我分别用 UTF-16LE(小端)和 UTF-16BE(大端)保存的「严」的十进制显示方式。而 FEFF 作为零宽度非换行空格仅仅是它出现在字节序列的中间时的作用,用户看起来就是一个空格,不过从 Unicode3.2 开始就只能规定 FEFF 只能出现在字节流的开头,用于标记字节序(大小端)。
最后是我自己的补充,UTF-8、UTF-16 各自的应用场景和优缺点。
UTF-8 的优势是:它以单字节为单位用 1~4 个字节来表示一个字符,兼容了 ASCII,在数据传输和存储过程中节省了空间,其二是UTF-8 不需要考虑大小端问题。这两点都是 UTF-16 的劣势。
不过对于中文字符,用 UTF-8 就要用3个字节,而 UTF-16 只需2个字节。而UTF-16 的优点是在计算字符串长度,执行索引操作时速度会很快。Java 内部使用 UTF-16 编码方案。而 Python3 使用 UTF-8。UTF-8 编码在互联网领域应用更加广泛。
以上就是我对字符编码的一点个人理解,不一定全部正确,但希望同样能给你带来一些思考。阮一峰老师的这篇文章虽然有些错误的地方,但只要不盲崇,带着怀疑的态度看待问题,也一定有不一样的收获。
关于字符编码:ascii、unicode与utf-8的更多相关文章
- 字符编码 ASCII,Unicode和UTF-8的关系
转自:http://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/00143166410626 ...
- 字符编码 ASCII unicode UTF-8
字符串也是一种数据类型,但是,字符串比较特殊的是还有一个编码问题. 因为计算机只能处理数字,如果要处理文本,就必须先把文本转换为数字才能处理.最早的计算机在设计时采用8个比特(bit)作为一个字节(b ...
- 彻底搞清楚字符编码: ASCII, ISO_8859, GB2312,UCS, Unicode, Utf-8
彻底搞清楚字符编码: ASCII, ISO_8859, GB2312,UCS, Unicode, U 1.ASCII: 0-127(128-255未使用),美国标准 2.IS0-8859-1(lati ...
- 字符编码(ASCII,Unicode和UTF-8) 和 大小端
本文包括2部分内容:“ASCII,Unicode和UTF-8” 和 “Big Endian和Little Endian”. 第1部分 ASCII,Unicode和UTF-8 介绍 1. ASCII码 ...
- 字符编码 ASCII,Unicode 和 UTF-8 概念扫盲
今天中午,我突然想搞清楚Unicode和UTF-8之间的关系,于是就开始在网上查资料. 结果,这个问题比我想象的复杂,从午饭后一直看到晚上9点,才算初步搞清楚. 下面就是我的笔记,主要用来整理自己的思 ...
- 字符编码 ASCII、Unicode和UTF-8的关系
摘抄自廖雪峰 教程 字符编码 我们已经讲过了,字符串也是一种数据类型,但是,字符串比较特殊的是还有一个编码问题. 因为计算机只能处理数字,如果要处理文本,就必须先把文本转换为数字才能处理.最早的计算机 ...
- 字符编码ASCII,Unicode 和 UTF-8
一直对编码的概念很模糊,今天抽空突然想了解下,就找到了这个文章,看完真的豁然开朗,必须感谢阮一峰先生. 一.ASCII 码 我们知道,计算机内部,所有信息最终都是一个二进制值.每一个二进制位(bit) ...
- Java 字符编码 ASCII、Unicode、UTF-8、代码点和代码单元
1 ASCII码 统一规定英语字符与二进制位之间的关系.ASCII码一共规定了128个字符的编码.例如,空格“SPACE”是32(二进制00100000),大写字母A是65(二进制01000001). ...
- 字符编码(ASCII,Unicode和UTF-8) 和 大小端(zz)
本文包括2部分内容:“ASCII,Unicode和UTF-8” 和 “Big Endian和Little Endian”. 第1部分 ASCII,Unicode和UTF-8 介绍 1. ASCII码 ...
- 字符编码ASCII、Unicode、GB
计算机的存储都是二进制的,那么我们平时看到的各种字符都需要通过按照一定的格式转换成为二进制才能在被计算机识别与处理.这个过程便成为编码.常见的编码方式有ASCII.Unicode.GB2312等. 1 ...
随机推荐
- TortoiseGit版本库中某个文件显示问号或叹号的问题解决办法
这是一个怪问题,原因就是文件名大小写与版本库管理的大小写不一致. 解决办法: 1.先把文件夹中的物理文件名改为版本库浏览器中显示的文件名(版本库浏览器中的文件名不知道怎么改),改了以后这个文件图标就变 ...
- hdu2795 Billboard(线段树单点修改)
传送门 结点中的l和r表示层数,maxx表示这层最多还剩下多少宽度.根据公告的宽度取找到可以放的那一层 找到后返回层数,并修改maxx #include<bits/stdc++.h> us ...
- selenium的基本定位方式总结
Selenium提供了8种定位方式. id name class name tag name link text partial link text xpath css selector 这8种定位方 ...
- python3实现合并两个有序数组
很早就听同学和师兄经常说刷题很重要,然而编程能力一直都很渣的我最近才开始从leetcode的初级算法开始.今天遇到的这道题虽然很简单,因为是头一次用自己的方法速度还不错,特此记录一下,还大神们请不要嘲 ...
- 使用vbox构建局域网络
update: 也可以启用DHCP自动分配IP地址.(看到过的某一篇博文写过要使用这个服务还得自己搭--就没有动手去实践一下直接手动分配了静态的IP.偶然尝试了一下发现动态IP分配和手动静态IP分配都 ...
- wf4 FlowChart 理解
1. 工作流是异步的,所以需要信号. 2. 需要创建 NativeActivity<T> 与整个FlowChart交互. 它的返回值 Result 正确含义是 该 Activity处理之后 ...
- poweroff命令详解
2019-02-17 基础命令学习目录首页 原文链接:https://www.cnblogs.com/Baron-Lu/p/6951297.html 在本篇中,我们会向你解释 shutdown. ...
- python所遇到的坑
我是在ubuntu中,自带的有python2,python3有安装了anaconda套件,所以python的版本很多,曾经想删除过不用的python. 先执行 sudo apt remove pyth ...
- python操作hive并且获取查询结果scheam
执行hive -e 命令并且获取对应的select查询出来的值及其对应的scheam字段 需要在执行语句中前部添加 set hive.cli.print.header=true; 这个设置,如下语句: ...
- Django_csrf
CSRF攻击介绍 CSRF 攻击可以在受害者毫不知情的情况下以受害者名义伪造请求发送给受攻击站点,从而在并未授权的情况下执行在权限保护之下的操作.比如说,受害者 Bob 在银行有一笔存款,通过对银行的 ...