Unicode 与 UTF-8_weixin_33725239的博客-程序员秘密

技术标签: python  java  

阮一峰老师对普及计算机基础技术功不可没,但毕竟老师不是神,因此也避免不了对某些概念有一些错误的理解,《字符编码笔记: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个字节。

再看来看这张图:

windows-notepad.jpg

阮老师对 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」 标识。

yan

上图就是我分别用 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 编码在互联网领域应用更加广泛。

以上就是我对字符编码的一点个人理解,不一定全部正确,但希望同样能给你带来一些思考。阮一峰老师的这篇文章虽然有些错误的地方,但只要不盲崇,带着怀疑的态度看待问题,也一定有不一样的收获。

参考链接:

  • https://en.wikipedia.org/wiki/Unicode
  • https://en.wikipedia.org/wiki/UTF-32
  • https://en.wikipedia.org/wiki/UTF-16
  • https://zh.wikipedia.org/wiki/%E4%BD%8D%E5%85%83%E7%B5%84%E9%A0%86%E5%BA%8F%E8%A8%98%E8%99%9F
  • https://zh.wikipedia.org/wiki/%E9%80%9A%E7%94%A8%E5%AD%97%E7%AC%A6%E9%9B%86
  • https://en.wikipedia.org/wiki/Universal_Coded_Character_Set
  • http://unicode.org/faq/utf_bom.html
  • http://www.fmddlmyy.cn/text6.html
  • http://stackoverflow.com/questions/643694/utf-8-vs-unicode
  • http://stackoverflow.com/questions/700187/unicode-utf-ascii-ansi-format-differences
  • https://www.meridiandiscovery.com/articles/unicode-and-character-encodings/
  • https://www.praim.com/character-encodings-linux-ascii-utf-8-iso-8859
  • http://stackoverflow.com/questions/4655250/difference-between-utf-8-and-utf-16
  • http://www.guokr.com/blog/83367/

转载于:https://my.oschina.net/mickelfeng/blog/967616

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_33725239/article/details/92487104

智能推荐

behemoth - 02_shuimuyq的博客-程序员秘密

#include #include #include int main(int argc, char *argv[]){ struct stat filestat; char cmd[16]; char *ptrpid = cmd + 6; pid_t pid; pid = getpid(); sprintf(cmd, "touch %d", pid); if (lst

docker安装Gogs_为了40不失业的博客-程序员秘密

转自:https://www.jianshu.com/p/2a7acb07b352一. 在宿主机创建目录无需指定特殊权限mkdir /opt/docker/gogs/二. 运行容器在此之前,先在mysql中创建gogs数据库. [注意,一定要先将mysql数据库的默认字符编码设置为utf8, 否则, gogs在自动创建表时, 会出现问题]docker run -d -p 10022:2...

用FPGA实现DVB码流分析功能的嵌入式设计方案_lokicui的博客-程序员秘密

 针对传统数字视频广播系统码流分析仪价格昂贵、使用不方便的问题,本文提出一种性价比较好的补充设计方案,它以通用的FPGA和RTOS为基础、基于嵌入式硬件平台来实现码流分析功能。文中还阐述了码流采集、码流分析和信息显示等多项关键技术。 码流分析仪可用作数字电视设备的调试工具,如检测MPEG编码器、复用器、调制解调器等设备的输入输出码流是否符合MPEG-2/数字电视广播(DVB)标准等。作为标

java -Xss缩写_滕青山YYDS的博客-程序员秘密

根据这篇文章https://www.cnblogs.com/ceshi2016/p/6077111.html知道:以 -X 开头的参数是和实现有关的,并不是适用于所有的 VMs,对于 -Xms 和 -Xmx 其含义为: -Xms:minimummemorysize for pileand heap -Xmx:maximum memory size for pile and heap   2. 对于具体含义的猜测: 最开始只有 -Xms 的参数,表示 `初始` mem...

Bert模型学习之环境配置(二)_bert环境配置_张张在努力_Lambda的博客-程序员秘密

Bert模型学习之环境配置(二)一、Python双版本的安装与环境配置1、下载地址:Download Python。首先提醒大家,tensorflow只能安装在 3.5-3.7版本的64位python上,切记。下载Python3.5版本,如Python 2.5.4:2.pip3报错:ModuleNotFoundError: No module named ‘pip._vendor.urllib3’(1)删除pip文件夹:进入python下pip的安装目录,比如这里我的目录是:“D:\python

uImage生成过程_xjzlq的博客-程序员秘密

当编译linux的时候,运行make uImage,如果一切正常,最后会生成uImage。如下具体讲解uImage生成过程:1. 生成uImag的工具mkimage由arch/arm/boot/Makefile中的MKIMAGE定义.Linux commit: e33936451 已经将此代码移到:scripts/Makefile.lib>.[c-sharp] view

随便推点

Notepad++安装emmet插件和python script插件问题_疯里来8愚里去的博客-程序员秘密

安装过程有一些注意事项,否则无法安装成功,我的notepad++版本为7.7.1,32位。(32位亲测可以,64位不知道)主要参考notepad++社区里面提供的安装教程https://notepad-plus-plus.org/community/topic/17366/how-to-install-emmet-plugin/2...

2021-06-22_fmikit_qq_41105502的博客-程序员秘密

Ansys twin builder如何用Simulink生成的FMU模型文件进行仿真项目场景:simulink仿真功能强大,可以满足我们很多的仿真需求,要将simulink中生成的组件导入到其他的软件当中(如Ansys twin builder),可以通过FMU来实现。本文演示如何通过simulink生成FMU文件并在Ansys twin builder中进行仿真。问题描述:主要问题:1.如何通过simulink生成FMU文件。2.simulink和twin builder中如何配置。解决方

飞行控制器Pixhawk简介_华清远见嵌入式学院的博客-程序员秘密

Pixhawk是一款由PX4开源项目设计并由3DR公司制造生产的高级自动驾驶仪系统。其前身是APM,由于APM的处理器已经接近满负荷,没有办法满足更复杂的运算处理,所以硬件厂商采用了目前最新标准的32位ARM处理器,它采用了ST Microelectronic先进的处理器及传感器技术且搭载了NuttX的实时操作系统,在模型自动控制方面有着出色的性能、灵活性和可靠性。

IDEA的这几个调试的骚操作,用了都说爽!_hello-java-maker的博客-程序员秘密

点击上方好好学java,选择星标公众号重磅资讯、干货,第一时间送达今日推荐:干掉 Navicat:这个 IDEA 的兄弟真香!个人原创100W+访问量博客:点击前往,查看更多...

js上传图片预览,php后台接收实例,已改写为多图上传预览_js文件上传 如何接收php_脚男footman的博客-程序员秘密

上传图片预览这个出自超实用的js代码段,关于图片的处理那一章,php后台接收是网上比较少这方面的资源或者写得很复杂,这里简单记录。都是基于js首先先调用两个文件夹animateManage.js和viewimg.js,可以在网上下载。相当好用。关键点是FileReader这个知识点,可以读取文件的相关信息,包括路径。这是个关键点。                    /*

linux做m3u8推流服务器,通过宝塔面板编译Nginx-rtmp-module模块搭建hls推流_高考语文大仙儿的博客-程序员秘密

本帖最后由 DevOps 于 2020-6-27 01:27 编辑根据目前宝塔7.3新版本提供的面板编译便捷页面来编译Nginx-rtmp-module模块安装Nginx的方法,目前全网没有看到谁提供教程,特发一教程。一、编译Nginx-rtmp-module模块环境第一步,登录面板——软件商店——运行环境——Nginx——安装QQ截图20200627010509.png (51.55 KB, 下...