写点什么

计算机字符编码的前世今生

发布于: 2 小时前

一、前言


有人丢给你下面这张图,如果你能清楚地说明它们之间的关系以及用途,那么你对字符编码的理解肯定过关了。



不知道看了上面这张图,是否有混乱的感觉,本文试着给你梳理、讲透这些孤立的几个单词之间联系......


二、关于字符编码,你所需要知道的


2.1 ASCII(寡头垄断时期)


计算机内部,所有信息最终都是一个二进制值。每一个二进制位(bit)有 0 和 1 两种状态,8 个二进制位称之为 1 个字节。把键盘上(如下图)所有按键的状态。



用 8 位二进制共 256 种表示绰绰有余。把所有的空格、标点符号、数字、大小写字母分别用连续的字节状态表示,一直编到了第 127 号,这样计算机利用 8 位二进制位(1 个字节)就可以用来存储英语的文字了,这就是大名鼎鼎的 ASCII(美国信息互换标准代码)。当时世界上所有的计算机都用同样的 ASCII 方案来保存英文文字。


2.2 非 ASCII 编码(汉字编码的发展)


伴随着互联网的兴起,计算机技术的发展,世界各地都开始使用计算机,但是很多国家用的不是英文,所适用的字母里有许多是 ASCII 里没有的。


为了可以在计算机保存他们的文字,决定采用 127 号之后的空位来表示这些新的字母、符号,还加入了很多画表格时需要用下到的横线、竖线、交叉等形状,一直把序号编到了最后一个状态 255。


从 128 到 255 这一页的字符集被称“扩展字符集”。此之后,贪婪的人类再没有新的状态可以用了。



随着计算机在中国流行时,已经没有可以利用的字节状态来表示汉字,况且有 6000 多个常用汉字需要保存。但这难不倒智慧的中国人民,我们就把那些 127 号之后的奇异符号们直接取消掉,并规定:一个小于 127 的字符的意义与原来相同,但两个大于 127 的字符连在一起时,就表示一个汉字,前面的一个字节(他称之为高字节)从 0xA1 用到 0xF7,后面一个字节(低字节)从 0xA1 到 0xFE,这样我们就可以组合出大约 7000 多个简体汉字了。


在这些编码里,我们还把数学符号、罗马希腊的字母都编进去了,连在 ASCII 里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的”全角”字符。而原来在 127 号以下的那些就叫”半角”字符了。中国人民看到这样很不错,于是就把这种汉字方案叫做 “GB2312”。GB2312 是对 ASCII 的中文扩展。


(图片来源于网络)


但是中国的汉字太多了,我们很快就就发现有许多人的人名没有办法在这里打出来。我们不得不继续把 GB2312 没有用到的码位找出来老实不客气地用上。后来还是不够用,于是干脆不再要求低字节一定是 127 号之后的内码,只要第一个字节是大于 127 就固定表示这是一个汉字的开始,不管后面跟的是不是扩展字符集里的内容。


结果扩展之后的编码方案被称为 GBK 标准,GBK 包括了 GB2312 的所有内容,同时又增加了近 20000 个新的汉字(包括繁体字)和符号。后来少数民族也要用电脑了,于是我们再扩展,又加了几千个新的少数民族的字,GBK 扩成了 GB18030


2.3 非 ASCII 编码


百花齐放,各自编码标准带来的问题


当时各个国家都像中国这样搞出一套自己的编码标准,结果互相之间谁也不懂谁的编码,谁也不支持别人的编码,就连大陆和台湾这样只相隔了 150 海里,使用着同一种语言,也分别采用了不同的 DBCS 编码方案。


当时的中国人想让电脑显示汉字,就必须装上一个“汉字系统”,专门用来处理汉字的显示、输入的问题,像是那个台湾的愚昧封建人士写的算命程序就必须加装另一套支持 BIG5


编码的什么“倚天汉字系统”才可以用,装错了字符系统,显示就会乱了套!这怎么办?


而且世界民族之林中还有那些一时用不上电脑的穷苦人民,他们的文字又怎么办?


(图片来源于维基百科)


2.4 Unicode


世界这么乱,得我来管管,大一统时期


ISO(国际标谁化组织)的国际组织决定着手解决这个问题。采用的方法很简单:废了所有的地区性编码方案,重新搞一个包括了地球上所有文化、所有字母和符号的编码!


他们打算叫它“Universal Multiple-Octet Coded Character Set”,简称 UCS, 俗称 “Unicode”。Unicode 相当于一个抽象层,给每个字符一个唯一的码点(code point)


用 0x000000 - 0x10FFFF 这么多的数字去对应全世界所有的语言、公式、符号。然后把这些数字分成 17 部分,把常用的放到 0x0000 - 0xFFFF,也就是 2 个字节,叫做基本平面 (BMP);从 0x010000 - 0x10FFFF 再划分为其他平面。


(图片来源于维基百科)


栗子:「v 维」



如果 「v 维」 这个字符串放到内存中就是 0x767ef4。问题来了,计算机怎么知道,几个字节代表一个字符呢?是 0x76 呢?还是 0x7ef4 呢?还是 0x767ef4?


Unicode 只是对信源编码,对字符集数字化,解决了字符到数字化的映射。接下来面临如何解决存储和传输的问题。


三、传输和存储


 用通信理论的思路可以理解为:

Unicode 是信源编码,对字符集数字化;

UTF-32、UTF-16、UTF-8 是信道编码,为更好的存储和传输。


3.1 UTF-32


UTF-32 编码,简单明了,码点值是多少,内存中就存多少,UTF-32 缺点很明显了,字母 A 原本只需要 1 个字节去存储,而现在却用了 4 个字节去存,大部分位置都是 0。


提问:我们为什么要多存那么多零呢?



3.2 UTF-16


问题「亮」 码点值是 20142,换成 16 进制就是 0x4eae,内存中是按字节进行编址的。所以我们是先存 4e 呢?还是 ae?


一个 Unicode 的码点值会对应一个数字,对于 Basic 平面的字符,我们直接把这个数字存到内存中。



UTF - 16 编码的时候,除本身的字节,为了区分大端序和小端序,最开头还多了两个字节,ff 和 fe。feff 代表大端序,fffe 代表小端序。


(Notepad 中的 BOM)


小知识:feff 和 fffe 也叫做 BOM,它可以区分不同编码。UTF-16 编码最小单位是两个字节,所以有字节序的问题,从而加了 BOM 来区分是大端序还是小端序。


3.3 UTF-8


UTF-8 顾名思义,是一套以 8 位为一个编码单位的可变长编码。会将一个码位编码为 1 到 4 个字节。一个码点值会生成 1 个或多个字节,然后把这些字节按顺序存就可以了。


小知识:UTF-8 无 BOM 或者 UTF-8 BOM。UTF - 8 的 BOM 是 EF BB BF  ,UTF-8 并不存在字节序的问题,因为它的最小编码单位就是字节。 


UTF-8 并不需要区分大端序还是小端序,所以可以不需要 BOM。如果加了 BOM,对于一些读取操作,它可能会把读取到的 BOM 认为是字符,从而造成一些错误。所以我们保存 UTF - 8 编码的文件时,最好选择无 BOM。



栗子:「知」



根据上表中的编码规则,「知」字的码位 U+77E5 属于第三行的范围:



这就是将 U+77E5 按照 UTF-8 编码为字节序列 E79FA5 的过程,反之亦然。



3.4 ANSI


「ANSI」指的是对应当前系统 locale 的遗留(legacy)编码。



Windows 里说的「ANSI」其实是 Windows code pages,这个模式根据当前 locale 选定具体的编码,比如简中 locale 下是 GBK。把自己这些 code page 称作「ANSI」是 Windows 的臭毛病。在 ASCII 范围内它们应该是和 ASCII 一致的。


3.1 扩展思考


问:在 java 中 char 型变量中能不能存贮一个中文汉字,为什么?


答:java 中使用的编码符号集是 Unicode(不涉及特定的编码方式,给每个符号分配一个二进制编码,目前已容纳容纳 100 多万个符号),而汉字已纳入 Unicode 字符集, 而 char 类型占两个字节,用来表示 Unicode 编码,所以是可以存储汉字的


问:tomcat 的中默认 ISO-8859-1 编码,如何解决 web 项目中的乱码问题?


答:

方式一:修改 tomcat 下的 conf/server.xml 文件

 <Connector port="8080"  protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8 useBodyEncodingForURI="true"/>
复制代码

复制代码


  • URIEncoding=”UTF-8“:即可让 Tomcat(默认 ISO-8859-1 编码)以 UTF-8 的编码处理请求参数。

  • useBodyEncodingForURI="true":是指请求参数的编码方式采用请求体的编码方式。


方式二: 


1)当使用字符流向浏览器发送页面信息时,默认查询的是 ISO-8859-1 码表

  • 设置 1:response.setCharacterEncoding("UTF-8")

  • 设置 2:response.setContentType("text/html;charset=UTF-8")


2)客户端请求服务器出现的中文乱码解决方式

  • POST 请求方式:浏览器当前使用什么编码,表单提交的参数就是什么编码,

    服务端处理:

    request.setCharacterEncoding("utf-8")。

  • GET 请求方式:

​String name=request.getParameter("name");//首先拿到参数的值​//得到的byte[] 再重新用utf-8去编码,即可得到正常的值​name=new String(name.getBytes("iso-8859-1")/**用参数的值用iso-8859-1来解码**/,"utf-8");String name=request.getParameter("name");//首先拿到参数的值​//得到的byte[] 再重新用utf-8去编码,即可得到正常的值​name=new String(name.getBytes("iso-8859-1")/**用参数的值用iso-8859-1来解码**/,"utf-8");
复制代码

复制代码

说明:tomcat 的 j2ee 实现对表单提交即 post 方式提示时处理参数采用缺省的 iso-8859-1 来处理,tomcat 对 get 方式提交的请求对 query-string 处理时采用了和 post 方法不一样的处理方式。


四、总结


回到前言中的那个问题,整理了下面这张图,不知现在的你是否对字符编码有了更清楚的认识......


用通信理论的思路可以理解为:

Unicode 是信源编码,对字符集数字化;

UTF-32、UTF-16、UTF-8 是信道编码,为更好的存储和传输。



参考资料


1、《深入理解计算机系统》


作者:vivo 互联网服务器团队-Zhu Wenjin

发布于: 2 小时前阅读数: 5
用户头像

官方公众号:vivo互联网技术,ID:vivoVMIC 2020.07.10 加入

分享 vivo 互联网技术干货与沙龙活动,推荐最新行业动态与热门会议。

评论

发布
暂无评论
计算机字符编码的前世今生