最近在看nodejs的源码,看到stream的实现里面满地都是encoding,不由想起以前看过的一篇文章——在前面的随笔里面有提到过——阮一峰老师的《字符编码笔记:ASCII,Unicode和UTF-8》

  好的文章有一个好处,你每次看都会有新的收获,它就像一款拼图,你每次看都能收获几块碎片,补齐之前的认识;而好文章与拼图不一样的是,好文章是一块无垠的世界,当你不愿局限于当前的眼界的时候,你可以主动走出去,外面要更宽广、更精彩的多。

  闲话说到这,开始聊聊所谓的编码。

  大家都知道,计算机只认识0和1,不认识什么abc。如果想让计算机显示abc,那么就要有一张1对1的表,用这张表告诉计算机,什么样的二进制串——比如(010101011100)——代表的是a,什么样的串代表的是b,这个表描述二进制串到符号的对应关系。ASCII就是这么一张最早也是最简单的表,这张表简单到包含128个符号,比如10个数字、26个小写字母、26个大写字母,和一堆标点符号(如英文句号、逗号等)还有控制字符(如回车、tab等)。那为什么是128,不是100或182?因为128正好用7个bit(一个0或者1为一个bit)表示。那么为什么不是256对或者更多?因为对于英语地区128个符号够用了,而在ASCII推出的那个年代(1967),大家还没开始着眼全球——这点从名字就可以看出来,ASCII = American Standard Code for Information Interchange(美国信息交换标准码),根本就没打算让别人上车。

  然后其他语言地区很快入场了,而且随着八位机的普及,1字节=8bit成为了共识,大家都盯上了多出来的128个位置。这个时代群魔乱舞,基本是个公司就想染指这块标准,乱象直到1985年才通过ISO/IEC 8859把EASCII确定下来。

  而在这个过程中,有一个地区的人根本就不跟你玩,就是意表文字地区,明白的说,主要是中日韩。开玩笑,256个位置,你全让出来都不够我做两句诗。老司机不带怎么办,只能自己开车了,首先是日本站出来,于1978年出台了最早的汉字编码,然后中国大陆、中国台湾、韩国都在80年代出台了自己的汉字编码。这个时候,大家各自玩各自的没什么问题,聚在一起,问题就来了。比如一篇文章中包含中日韩三种文字,一串01的组合在中国的编码对应的是某个字,在日本的编码对应的却是另一个字,那计算机最后到底显示哪个字,计算机也很为难。有冲突怎么办,开个会通通气吧,于是大家坐在一起成立了个组织,叫CJK-JRG(China, Japan, Korea Joint Research Group)。虽然这个组织折腾了很多年,而且最终提案也被否决了,但是为另一个方案提供了足够的信息,就是Unicode。

  Unicode项目于1987年启动,在吸收了CJK-JRG的方案后于1992年6月份发布1.0.1版(之前的1.0.0没有包含汉字),迄今为止还在增修,最新的版本是2017.6.20公布的10.0.0。最早的Unicode被设计为16bit,即每个符号占2byte,最多表示65536个符号。而后随着内容的增加,又基于原有设计不变的原则,将最早的65536个字符集合称为基本多文种平面(Basic Multilingual Plane, BMP),并添加16个辅助平面(总共支持65536 * 17 = 1114112个符号)。这样一来,原来的16bit就不够用了,需要21bit才能准确描述一个符号,相当于3byte不到,但是为了以后扩展方便及统一,辅助平面的符号要求使用4byte描述。

  Unicode解决了全世界人民用一套符号编码的问题,但却没有解决另一个问题,就是怎么存储的问题。按照一般的想法,所有的符号都必须以最长的Unicode符号的标准来存储,也就是4byte,这样才不会有信息丢失。但是这样的话,对于全部是英文的文档,要浪费掉3/4的区域,对于大多数汉字,即BMP中的汉字,也要浪费掉1/2的区域。所以野蛮的使用4byte进行存储是不可取的,那么就要设计一套变长的规则来处理不同类型的符号,这时候UTF8、UTF16等就应运而生了,也就是说UTF8、UTF16是Unicode的一种实现方案(标准的说法,是Unicode字符编码五层模型的第三层,如果你对五层模型感兴趣,跳转《刨根究底字符编码》)。

  先说UTF8,UTF8是完全变长的,占用1-6byte,乍一看,怎么比直接用4byte存储还多出一半呢?其实占用4byte的情况是很少的,少到在几乎可以忽略不计,而5、6byte基于当前16个辅助平面的情况下还用不上。一般来说,英文占用1byte,中文占用3byte(CJK-JRG最早提供给Unicode的20000多个符号位于位于U+4E00–U+9FFF,这块区域的符号统一都占3byte),所以一般来说使用UTF8可以节省1/4到3/4的存储区域。这样似乎解决了存储的问题,但却带来了另一个问题,即识别的问题。比如我给你3byte的二进制信息,告诉你这代表了一个字,那你肯定很快能知道是什么字,但我如果不告诉你字数呢,是一个字,两个字,还是三个字?你根本识别不出来这一串二进制是什么。这就是变长的方案需要解决的第二个问题,告诉读取方哪几个byte是一组的,UTF8的规则很简单,我直接从阮老师的博客里搬运过来。

)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。
)对于n字节的符号(n>),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。
Unicode符号范围 UTF-8编码方式
(十六进制) (二进制)
0000 0000-0000 007F 0xxxxxxx
0000 0080-0000 07FF 110xxxxx 10xxxxxx
0000 0800-0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-001F FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
0020 0000-03FF FFFF 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
0400 0000-7FFF FFFF 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

  简单的说,你收到很多个byte的二进制,从第一个byte开始读,数第一个0出现之前的1,有几个1就代表前面几个byte是一组的,0个1就代表当前的这个byte孤家寡人一个。然后跳过这个组的所有byte,继续之前数1的环节。分好组后,按组找到上表右边的规则,把规则内x的位置保留下来,01的位置全部扔掉(01代表的位置是UTF8的元数据,x代表的位置才是Unicode的数据),拼成新的二进制串,这个串就是Unicode了。举个栗子:

         01101111 01101100

  以上有11个byte,我们从第一个byte11100110开始,数第一个0前面的1的数量,有3个1,代表3个byte是一组的。然后我们跳过这3个,第四个byte是11100110,继续数1得出有3个1,然后又给这3个byte分组。跳过这三个,到了第7个byte,这往后的5个byte都是以0开头,说明每个byte为1组。现在我们分好组了,有7个组,分别是

[11100110, 10001000, 10010001], [11100110, 10011000, 10101111], [01110100], [01100001], [01110010], [01101111], [01101100]

  现在我们按组找到表右对应的行,第一组、第二组对应第五行,其他组对应第三行,我们把行内x对应的位置保留,10的位置删除,得到新的数组

[0110, 001000, 010001], [0110, 011000, 101111], [1110100], [1100001], [1110010], [1101111], [1101100]

  然后把组内的二进制串起来得到Unicode

[0110001000010001], [0110011000101111], [1110100], [1110100], [1100001], [1110010], [1101111], [1101100]

  这时候我们再按byte进行拆分以便阅读,并且在高位补0

[01100010 00010001], [01100110 00101111], [01110100], [01110100], [01100001], [01110010], [01101111], [01101100]

  再转换成16进制

[62 11], [66 2F], [74], [61], [72], [6F], [6C]

  这时候我们打开F12,在控制台输入对应的Unicode(语法要求必须使用4位16进制数字)

'\u6211\u662f\u0074\u0061\u0072\u006f\u006c'

  得到了对应的字符串“我是tarol”。

  好了,以上是UTF8的内容,之所以叫UTF8是因为这个规则下的符号,最少占8bit。那么UTF16就好理解了,在这个规则下,每个符号最少占16bit。UTF16的规则说起来更简单,当符号位于BMP中时,占用2byte,在符号位于辅助平面时,占用4byte。那既然是变长的,又碰到了上面的问题,怎么识别这一块是4byte为一组还是2byte为一组?

  这里就要提到Unicode的保留区块了,Unicode规定从U+D800到U+DFFF之间是永久保留不赋予任何符号的。也就是正常情况下,2byte如果落在这个范围内,那么就是Unicode的非法字节。而UTF16的做法就是把辅助平面的Unicode码进行处理,变成4个字节,并且两两落在非法区域内,读取方读到了非法字节,就可以界定这里是4byte为一组,不然就是2byte为一组。那么UTF16这个转换的算法又是怎样的呢?

  1. 首先按现在17个平面的限制,辅助平面的码位是U+10000到U+10FFFF,我们得到了一个辅助平面的Unicode码时,先减去BMP的码数0x10000,得到的数介于0到0xFFFFF之间,最多用20bit表示
  2. 然后我们把20bit从中间隔开,分为高位的10bit和低位的10bit
  3. 我们知道10bit的取值范围是0到0x3FF,高位的10bit加上固定值0xD800,得到的值叫做前导代理(lead surrogate),范围是0xD800到0xDBFF
  4. 低位的10bit加上固定值0xDC00,得到的值叫做后尾代理(tail surrogate),范围是0xDC00到0xDFFF。这样一来,不仅高位和低位都落在了保留区块内,而且彼此还做了区分。

  还是举个例子。

  

所谓编码--泛谈ASCII、Unicode、UTF8、UTF16、UCS-2等编码格式的更多相关文章

  1. 理解记忆三种常见字符编码:ASCII, Unicode,UTF-8

    理解什么是字符编码? 计算机只能处理数字,如果要处理文本,就必须先把文本转换为数字才能处理.最早的计算机在设计时采用8个比特(bit)作为一个字节(byte),所以,一个字节能表示的最大的整数就是25 ...

  2. 字符编码:ASCII,Unicode,UTF-8

    1.ASCII码美国制定的一套字符编码,对英语字符和二进制位之间的关系,做了统一规定.ASCII码一共规定了128个字符(包括32个不能打印出来的控制符号)的编码,占用一个字节,字节的最前面1位统一为 ...

  3. 关于编码:Unicode/UTF-8/UTF-16/UTF-32

    关于编码,绕不开下面这些概念 ①Unicode/UTF-8/UTF-16/UTF-32 ②大小端字节序(big-endian/little-endian) ③BOM(Byte Order Mark) ...

  4. 字符编码 ASCII unicode UTF-8

    字符串也是一种数据类型,但是,字符串比较特殊的是还有一个编码问题. 因为计算机只能处理数字,如果要处理文本,就必须先把文本转换为数字才能处理.最早的计算机在设计时采用8个比特(bit)作为一个字节(b ...

  5. 细说:Unicode, UTF-8, UTF-16, UTF-32, UCS-2, UCS-4

    1. Unicode与ISO 10646 全世界很多个国家都在为自己的文字编码,并且互不想通,不同的语言字符编码值相同却代表不同的符号(例如:韩文编码EUC-KR中“한국어”的编码值正好是汉字编码GB ...

  6. 浅显总结ASCII Unicode UTF-8的区别

    如果觉得此地排版不好,欢迎访问我的博客 浅显总结ASCII Unicode UTF-8的区别 制作表单时,为了追求更好的用户交互体验,常常会有提示性的内容,比如提醒用户字符的限制.由于英文,中文字符的 ...

  7. 【转】【编码】ANSI,ASCII,Unicode,UTF8之一

          不同的国家和地区制定了不同的标准,由此产生了 GB2312.GBK.GB18030.Big5.Shift_JIS 等各自的编码标准.这些使用多个字节来代表一个字符的各种汉字延伸编码方式,称 ...

  8. ASCII,unicode, utf8 ,big5 ,gb2312,gbk,gb18030等几种常用编码区别(转载)

    原文出处:http://www.blogjava.net/xcp/archive/2009/10/29/coding2.html 最近老为编码问题而烦燥,下定决心一定要将其弄明白!本文主要总结网上一些 ...

  9. 【转】关于字符编码,你所需要知道的(ASCII,Unicode,Utf-8,GB2312…)

    转载地址:http://www.imkevinyang.com/2010/06/%E5%85%B3%E4%BA%8E%E5%AD%97%E7%AC%A6%E7%BC%96%E7%A0%81%EF%BC ...

随机推荐

  1. NET中解决KafKa多线程发送多主题的问题

    一般在KafKa消费程序中消费可以设置多个主题,那在同一程序中需要向KafKa发送不同主题的消息,如异常需要发到异常主题,正常的发送到正常的主题,这时候就需要实例化多个主题,然后逐个发送. 在NET中 ...

  2. PyCharm中Directory与Python package的区别

    对于Python而言,有一点是要认识明确的,python作为一个相对而言轻量级的,易用的脚本语言(当然其功能并不仅限于此,在此只是讨论该特点),随着程序的增长,可能想要把它分成几个文件,以便逻辑更加清 ...

  3. mysql 查询性能优化第一章 为什么查询速度会慢

    一 为什么查询速度会慢 在尝试编写快速的查询之前,咱们需要清除一点,真正重要的是响应时间.如果把查询看成是一个任务,那么它由一系列子任务组成,每个子任务都会消耗一定的时间.如果要有 优化查询,实际上要 ...

  4. 【NO.12-2】jmeter-执行脚本

    //拿jmeter举例 //要进入到jmeter工具的bin目录 //当使用其它的开源测试工具的时候,也可以参考上面这一点,即:进入到bin目录 //如果工具本身不包含bin文件,那么在工具的1级目录 ...

  5. HDU 5067 Harry And Dig Machine:TSP(旅行商)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5067 题意: 给你一个n*m的地图,地图上标着对应位置的石子数.你从左上角出发,每次可以向上下左右四个 ...

  6. MT过安全狗增加用户

    记一次入侵时发现可以增加用户,但是由于狗的存在,无法赋予管理员权限 此时就进行类似使用注册表的复制超级管理员的操作 使用MT的克隆功能, net user test test /add mt -clo ...

  7. Fiddler模拟重发请求

    在测试的过程中会碰到模拟请求的重发或者修改请求的参数进行请求模拟发送 一.Reissue Sequentially 模拟多次重发 1.启用后fiddler:PC端或手机端创建某条数据后,session ...

  8. 最新eclipse国内镜像站,比ustc等站点资源新。

    http://mirrors.neusoft.edu.cn/ 东软信息学院的镜像站,上面可以看到同步时间和状态很不错. 之前为了找最新的镜像站下载babel_language_packs r0.15. ...

  9. JAVAWEB项目如何实现验证码 (转)

    JAVAWEB项目如何实现验证码 2012-12-21 21:19 56026人阅读 评论(36) 收藏 举报 .embody { padding: 10px 10px 10px; margin: 0 ...

  10. MySQL、PHP入门

    登录MySQL     mysql -hlocalhost-uroot -proot 退出MySQL     exit 每条语句后必须加分号:----------------------------- ...