开发者社区> Linux技术干货> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

作为一个程序员,告诉你一些编码知识

简介:
+关注继续查看

计算机只认 0 和 1 ,所有的影像和字符最终都会转换成计算机能够认识的二进制。一个二进制位(bit)可以表示两种状态 0 和 1 ,一个字节(byte)由八个二进制位组成,所以一个字节一共可以表示256( 2^8 )种状态。
在谍战剧里,我们经常看到这样一个桥段,特工人员,千辛万苦拿到一条信息,打开一看是一串数字,然后赶紧跑到一个秘密地方,拿出一个密码本(也可能是一本唐诗选),按照一定规则(只有自己人知道),比如第一个数字表示页数,第二个数字表示行数,第三个数字表示第几个字,逐一将信息翻译出来。如果这个过程中用了错误的密码本,或者不知道规则,那么将会解码失败。
作为一个程序员,告诉你一些编码知识作为一个程序员,告诉你一些编码知识

计算机的编解码过程跟上面的过程是一样一样的。

计算机只认 0 和 1 ,所有的影像和字符最终都会转换成计算机能够认识的二进制。一个二进制位(bit)可以表示两种状态 0 和 1 ,一个字节(byte)由八个二进制位组成,所以一个字节一共可以表示256( 2^8 )种状态。如果我们规定每种状态代表一个字符,那么一个字节就可以表达出 256 个字符。

ASCII
计算机是由美国人发明的,所以在最初设计编码的时候,就只考虑了英文的编码。英文字符很少,加上一些特殊字符,一共也就100个左右,确切的说是128个。这样的话用一个字节进行编码就完全够了,不仅够用了,而且还富裕出一位,即第一位一直没有参与编码,统一定为 0 。这就是所谓的 ASCII 编码。在 ASCII 编码中,空格 SPACE 是 32 (二进制 00100000 ),大写的字母 A 是 65 (二进制 01000001 )。

非ASCII
随着计算机的普及,欧洲也开始普及计算机,欧洲人发现 ASCII 规定的 128 个字符不能满足他们的使用,比如,在法语中,字母上方有注音符号,就无法用 ASCII 码表示。于是,一些欧洲国家就决定,把字节中闲置的第一位编入新的符号。比如,法语中的 é 的编码为 130 (二进制 10000010 )。这样一来,这些欧洲国家使用的编码体系,最多可以表示 256 个符号。这就是大家经常见到的 ISO-8859-1 编码,也叫 Latin1 编码。

中文编码
随着计算机的普及,国人也开始使用计算机,但是发现按照之前的编码方式,根本就没有汉字什么事儿,也就是计算机根本没办法认识汉字。

GB2312
为了能够让计算机认识汉字,我们决定对汉字进行编码,本着敢想敢干的精神,我们规定用两个字节表示一个汉字。

具体规则是这样的:一个小于 127 的字节代表的意义与原来的 ASCII 相同,但两个大于 127的字节连在一起时,就表示这是一个汉字,前面的一个字节称为高字节,后面一个字节称为低字节,这样我们就可以组合出 6763 个简体汉字。这就是大家常说的 GB2312 编码。

GBK
很显然 GB2312 编码的 6763 个汉字,并不能适应所有的使用场景,比如“喆”字就不再其中,于是在 GB2312 的基础上又进行了新的扩展,规定只要第一个字节是大于 127 的就OK,至于第二个字节是大于 127 还是小于 127 都无所谓了。经过这样的改动之后,收录的汉字及符号就可以达到 2W 多个,这就是我们常说的 GBK 编码。

再后来,人们继续对第二个字节进行扩展,发展出了 GB18030 编码,比 GBK 又多出了一些字符编码。

至此,所有的汉字编码都是用两个字节表示的,但是英文是用一个字节表示。上了一些年纪的程序员都体验过,一个汉字算两个英文字符的经历。

BIG-5
上面提到的都是简体中文编码,虽然 GBK 及 GB18030 包含了部分繁体字,但是也不全面,于是台湾同胞就发了专门支持繁体字的 Big5 编码,也就是大家经常说的大五码。

一个小问题
不知道大家有没有注意到一个问题,在单字节编码的时候,对于那些大于 127 小于 256 的编码,在不同的国家代表的字母很可能不一样。比如, 130 在法语编码中代表了 é ,在希伯来语编码中却代表了字母 Gimel (ג) ,在俄语编码中又会代表另一个符号。在汉字的双字节编码中也存在这样的问题,比如 BIG5 编码跟 GBK 编码都是双字节编码,但是代表的汉字却不一样。

这就相当于,同样一串二进制数值,A特工组织按照他们的规则解析出来可能是“你好”,而B特工组织按照他们的规则解析出来可能是“滚蛋”。特工组织之间的翻译标准不一样是相当有必要的,但是计算机的编码规则如果各不相同就比较麻烦了。比如你跟台湾的志玲姐姐聊天,志玲姐姐用 BIG5 编码给你发了一封信,然后你用 GBK 去解码,……,也许就没有然后了。

Unicode
为了解决上面的问题,有个叫 ISO 的国际标准组织,决定放弃所有区域性编码,如 BIG5 , GBK 等,重新制定一个新的编码,这个编码集将包含所有字符的编码,这样大家就都统一了,这套编码的英文全称“Universal Multiple-Octet Coded Character Set”,简称UCS, 俗称 “Unicode“。 Unicode 的出现相当于秦始皇对度量衡跟货币进行了统一。

Unicdoe 按照日常字符的使用频繁度划分了 17 个平面,编号为 0-16 , 0 号平面称为基本多语言平面(Basic Multilingual Plane,简称 BMP ),包含了日常使用最频繁的字符,编码范围从 0000 到 FFFF ,这样该平面可以表示 2^16=65536 个字符;其它平面的编码范围也是从 0000 到 FFFF ,所以其它平面也可以编码 65535 个字符,这样 17 个平面一共可以编码 17×65,536 = 1,114,112 个符号。

我们最常用的 Unicode 编码使用的是多语言平面的编码,即所有字符都用两个字节进行编码(其它平面可能需要三个或四个字节)。举个例子比如中国的'中'字 Unicode 码是 4E2D ,小写'a'的 Unicode 码是 0061 .
这里面存在两个问题,如果所有英文字符都是按照 Unicode 编码,那么会出现浪费存储空间的问题。明明一个字节可以搞定的事情,偏偏要用两个字节。

第二个问题就是计算机如何知道这是 Unicode 编码还是 ASCII 编码,也就是 2 个字节表示的一个字符,还是 2 个字符呢。

UTF
UTF 的全称是 Unicode Transformation Format ,也就是 Unicode 的转换格式。上面提到了,如果直接使用 Unicode 码进行存储会存在浪费空间的问题,而 UTF-8 的出现就是为了解决该问题, UTF-8 使用变长的方式存储 Unicode 码,也就是英文字符继续使用一个字节进行存储,但是汉字要使用 3 个字节。那么 UTF-8 是如何做到的呢。

首先,对于单字节的符号,字节的第一位设为 0 ,后面 7 位为这个符号的 Unicode 码。因此对于英语字母, UTF-8 编码和 ASCII 码是相同的。

其次,对于 n 字节的符号( n > 1 ),第一个字节的前 n 位都设为 1 ,第 n + 1 位设为 0 ,后面字节的前两位一律设为 10 。剩下的没有提及的二进制位,全部为这个符号的 Unicode码。
下表总结了编码规则,字母 x 代表可用的编码位。

Unicode符号范围(十六进制) UTF-8编码方式(二进制)<>pre
0000 0000-0000 007F 0xxxxxxx
0000 0080-0000 07FF 110xxxxx 10xxxxxx
0000 0800-0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

根据上表,对 UTF-8 编码进行解读会发现,如果一个字节的第一位是 0 ,则这个字节单独就是一个字符;如果第一位是 1 ,则连续有多少个 1 ,就表示当前字符占用多少个字节。

举个例子
假设“hello世界”这样一个字符串,他们的 Unicode 的编码分别是

1h--0068
2e--0065
3l--006C
4l--006C
5o--006F
6世--4E16
7界--754C
按照 UTF-8 的编码规则可以得到如下 UTF-8 编码

1h--01101000
2e--01100101
3l--01101100
4l--01101100
5o--01101111
6世--11100100-10111000-10010110
7界--11100111-10010101-10001100
可以看到用 UTF-8 编码之后,英文字符占用一个字节,而汉字占用了三个字节,一共需要 11个字节,而如果直接存储 Unicode 码则需要 14 个字节。 UTF-8 编码对于英文来说节省了很大空间,但是对于中文来说增加了空间。

Little endian 和 Big endian
上面提到 Unicode 是用两个字节表示字符,如果第一个字节在前,就是"大端方式"(Big endian),第二个字节在前就是"小端方式"(Little endian)。'世'字的 Unicode 码是 4E16 ,一个字节是 4E ,一个字节是 16 , 存储的时候如果 4E 在前就是大端存储,如果是 16 在前就是小端存储。

那么计算机是怎么知道一个文件是采用哪种编码方式呢?

Unicode 规范定义,每一个文件的最前面分别加入一个表示编码顺序的字符,这个字符的名字叫做"零宽度非换行空格"(zero width no-break space),用 FEFF 表示。这正好是两个字节,而且 FF 比 FE 大 1 。

如果一个文本文件的头两个字节是 FE FF ,就表示该文件采用大头方式;如果头两个字节是 FF FE ,就表示该文件采用小头方式。

总结
UTF-8 编码是基于 Unicode 字符集的一种编码实现。现在几乎所有的编程语言和操作系统都支持 Unicode 编码,使用 Unicode 编码之后,再也不会出现上文提到的一个汉字等于两个英文字符的尴尬局面。

GBK , BIG5 等都属于区域性编码只能在固定范围内使用,比如 GBK 只适合在简体中文环境使用,虽然 GBK 相比于 UTF-8 更节省空间,但现在全世界都变成地球村了,所以还是建议大家都使用 UTF-8 编码。

ANSI :在 window 下,如果我们用记事本打开文档,经常会见到 ANSI 编码方式,这是 Windows 默认的编码方式。对于英文文档采用 ASCII 编码,对于简体中文文档采用 GB2312 编码(只针对 Windows 简体中文版,如果是繁体中文版会采用 Big5 码)。

原文来自:https://news.51cto.com/art/202003/613297.htm

本文地址:https://www.linuxprobe.com/encoding-knowledge.html

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
使用 fyne 编写一个计算器程序
云栖号资讯:【点击查看更多行业资讯】在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来! 简介在上一篇文章中,我们介绍了一个 Go 的高颜值 GUI 库fyne。本文接着上一篇,介绍如何使用fyne编写一个简单的计算器程序。
906 0
临时起异,要进入C++领域耍一个程序
没办法。两周之内可以搞定吧。 就一个SESSION 0的问题。 网上有类似源码,调一下应该就可以吧。。保佑顺利。 基本语法都还记得,快N年啦。。。 #include #include #include using namespace std; int main() { ...
598 0
一个程序员的2015年总结
又到了年末写总结的时候。每年写总结时的心情都不一样,有的时候收获满满,有的时候诚惶诚恐,有的时候有些许遗憾….今年写年终总结,心情则特别复杂,这一年经历的事情不可谓不多,自己的成长也不可谓不大,但自己却时不时被一种焦虑的情绪烦扰。
925 0
一个求随机数的程序
程序说明: 这是一个求随机数的程序,随机数的个数为N,可以手动输入,因为我定 义的 数组 是ran[1000],所以我的是产生0~1000内的不重复的随机数。                  #include #include//#include//#include#includevoi...
539 0
程序编码
  看大图请点击:这 里  
519 0
+关注
Linux技术干货
《Linux就该这么学》是一本注重于实用性的Linux系统技术自学书籍,自基础篇公布后网站每天日常阅读量已经超过10000多人,25万多名忠实粉丝读者,是目前国内人气增速最快的IT书籍。您可以在本网站内免费在线阅读书籍的全部章节及最新内容,今后的进阶篇也将会一如既往免费、完整的提供给亲爱的读者们在线
文章
问答
文章排行榜
最热
最新
相关电子书
更多
低代码开发师(初级)实战教程
立即下载
阿里巴巴DevOps 最佳实践手册
立即下载
冬季实战营第三期:MySQL数据库进阶实战
立即下载