联通不如移动的故事

在编码界一直流传着联通不如移动的一个故事。。。

请不要误会,联通和移动和本篇文章所说的编码确实没什么关系,但请出联通和移动帮忙做个小实验,再来仔细说说编码。

在Windows系统下,在桌面上右键新建一个记事本文件,打开它输入“联通”两个汉字,Ctrl+S保存并关闭。

双击再次打开它,看到了什么?奇怪,文字怎么变成乱码了?

好吧,再次新建一个文件,这回输入“移动”保存再试试。神奇,移动居然完美显示。

好了,不说什么故事了,这个有趣的现象正是为了聊聊计算机中“编码”的那些事,之后再解释为什么“联通不如移动”。

聊聊字符编码的发展史

在计算机中,所有存储的数据都由二进制表示。字母、数字、字符这些都不例外,计算机中最小的单位就是二进制位(0和1),8个位表示一个字节,因此8个二进制位就可以排列组合出256种状态,也就是理论上可以表示出256种字符,而由哪些二进制位表示哪些字符,这就是由人来决定的了,也就是人们制定出的各种“编码”。

电脑这种东西最早由老外发明,外国人使用的英语只有26个字母,再加上标点、数字和一些符号也不会太多,因此英文通常用ASCII编码来表示。

ASCII码

ASCII码最开始只在美国使用,组合出的256种状态中,第0~32中规定了特殊用途,一旦终端、打印机遇上约定好的这些字节被传过来时,就要做一些约定的动作,比如遇到0×10, 终端就换行等等。

又把所有的空格、标点符号、数字、大小写字母分别用连续的字节状态表示,一直编到了第 127 号,这样计算机就可以用不同字节来存储英语的文字了。

记得当初学习C语言的时候,就清楚的知道了一些常用的ASCII码值,比如大写A是65,小写a是97等。


这128个符号(包括32个不能打印出来的控制符号),只占用了一个字节的后面7位,最前面的一位统一规定为0。

英文可以表示了,但是世界上除了英文还有很多语言。我们的中文文字浩如烟海,仅仅靠这8个二进制位远远不够,怎么办?

GB2312

且不说中文,在欧洲有些国家的语言中也有一些特殊的字母,比如俄文希腊文等。于是便使用127号之后的空位继续表示他们的字母。当然,由于每个国家的语言不同,就越来越乱,比如130在法语中是字母 é,但是在希伯莱语中130却是他们的字母 ג。

我们的中文就更难办了,即使把所有的位都用上,也表示不完成千上万的汉字,于是我们自己也制定了一套中文的编码GB2312。

中国为了表示汉字,把127号之后的符号取消了,规定:

  • 一个小于127的字符的意义与原来相同,但两个大于 127 的字符连在一起时,就表示一个汉字;
  • 前面的一个字节(他称之为高字节)从0xA1用到0xF7,后面一个字节(低字节)从 0xA1 到 0xFE;
  • 这样我们就可以组合出大约7000多个(247-161)*(254-161)=(7998)简体汉字了。
  • 还把数学符号、日文假名和ASCII里原来就有的数字、标点和字母都重新编成两个字长的编码。这就是全角字符,127以下那些就叫半角字符。

把这种汉字方案叫做 GB2312。GB2312 是对 ASCII 的中文扩展。

GBK

再后来,发现了GB2312虽然解决了中文编码的问题,但是仍有不足。

GB2312表示的中文有时不够,有些字并不是生僻字,但是没有收录其中,当时有个小插曲,我当时在高考报名的系统中查询成绩的时候报不出我的名字,只能报出我的姓,正是因为我的名字“玥”字不在GB2312的编码范围,因此没有。

于是干脆不再要求低字节一定是 127 号之后的内码,只要第一个字节是大于 127 就固定表示这是一个汉字的开始,又增加了近 20000 个新的汉字(包括繁体字)和符号。

这就是更全面的GBK编码。

Unicode

随着发展,每个国家都对自己的语言编出一套自己的编码,真是混乱不堪,我们不知道别人用什么编码,别人也不知道我们用什么编码,于是标准组织出手了。

ISO标准组织看到了乱象,制定了一套Unicode编码以解决这种混乱的局面,它的制定简单粗暴,不是全世界的语言多么,我干脆就规定,所有的字符都给我用两个字节表示(两个8位一共16位),对于 ASCII 里的那些 半角字符,Unicode 保持其原编码不变,只是将其长度由原来的 8 位扩展为16 位,而其他文化和语言的字符则全部重新统一编码。

从 Unicode 开始,无论是半角的英文字母,还是全角的汉字,它们都是统一的一个字符。同时,也都是统一的两个字节。

UTF8

Unicode的制定是在1990年,正式使用在1994年,那个年代在现在来看简直是远古时期,那时由于互联网并不发达并没有推广开。

随着互联网的发展,为了解决Unicode传输问题,于时面向众多的UTF标准出现了。

  • UTF-8 就是在互联网上使用最广的一种 Unicode 的实现方式
  • UTF-8就是每次以8个位为单位传输数据
  • 而UTF-16就是每次 16 个位
  • UTF-8 最大的一个特点,就是它是一种变长的编码方式
  • Unicode 一个中文字符占 2 个字节,而 UTF-8 一个中文字符占 3 个字节
  • UTF-8 是 Unicode 的实现方式之一

因为UTF8是Unicode的实现方式之一,它们之间是互通的,就是说Unicode编码可以传换为UTF8,它有一套对应规则:

Unicode符号范围(16进制) UTF8编码(2进制)
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

可以看到,对于单字节的符号,字节的第一位设为0,后面7位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的(见上面表格的第一行)。

对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的 Unicode 码。

说的有些抽象,举个例子吧,比如来了一个汉字,电脑是怎么知道的它是用UTF8编码的呢?

因为汉字用三个字节表示(别再问为什么用三个字节表示了,这是规定),因此第一个字节的前三位都为1,第四位设为0,后面的位都以10开头,所以它肯定长这个样子:1110xxxx 10xxxxxx 10xxxxxx。

OK,电脑按照这个规则一看明白了,来的是个汉字!

不如再举个例子,从Unicode编码表中查出一个汉字对应的编码,把它转换为UTF8试一试,就用我的名字“玥”字吧,它的Unicode编码为\u73a5

首先第一步把16进制转换为2进制,它的值是111001110100101,那怎么拆分这个2进制的值呢?因为UTF8都是后6位为这个字符的Unicode的码,所以我们从右往左数6位给一一对应上,不足的位补0就好了。

这样就得出了“玥”字的UTF8编码:11100111 10001110 10100101

作为开发人员完全可以用代码实现一下,这里用node.js真实的实现一下转码:

function transferToUTF8(unicode) {code = [1110, 10, 10];let binary = unicode.toString(2); //转为二进制code[2] = code[2] + binary.slice(-6); //提取后6位code[1] = code[1] + binary.slice(-12, -6); //提取中间6位code[0] = code[0] + binary.slice(0, binary.length - 12).padStart(4, '0'); //取剩余开始的位,不够补0code = code.map(item => parseInt(item, 2)); //把字符串转换为二进制数值return Buffer.from(code).toString(); //利用Buffer转转为汉字
}console.log(transferToUTF8(0x73a5));

运行结果:

以上代码定义了一个transfer函数,参数接收一个16进制值,它代表了一个Unicode字符,transfer函数内部先转换为二进制,并按照UTF-8的规则转换为相应的UTF-8编码,最后,利用node.js的Buffer最终转码成汉字,可以看到,已经正确输出了汉字“玥”。

以上,就是简单分析了Unicode和UTF-8的转换关系。

为什么联通不如移动?

故事就要讲完了,说了这么多编码的事现在可以回头看看开篇为什么联通变成了乱码,因为在Windows的记事本中文默认的保存编码为GB2312,通过查询可以查到汉字“联”对应的GB2312编码为uc1aa,转换为二进制是1100000110101010,正好是16位两个字节,按8位拆成两组正好与UTF8的第二种编码格式对应上了:110xxxxx 10xxxxxx,这样再次打开记事本的时候Windows扫描文件内容,它就会认为这是UTF-8编码的文件,而不是GB2312!此时此刻按照UTF-8来解析文件内容当然出现了乱码。

这时可以重新另存为文件,把文件格式改为GB2312来保存,现次打开“联通”终于显示了。

这个例子很极端,可以说“联通”二字的编码正好是个巧合,但是搞明白了编码的细节,更有助于我们在开发中遇到问题可以快速理解其实质,并加以解决,在此记下笔记,与大家共同学习提高。

从一个小故事聊聊字符编码那些事相关推荐

  1. 如何查询一个表中除某几个字段外其他所有的字段_一个小故事告诉你:如何写好数据分析报告?...

    关注并将「人人都是产品经理」设为星标 每天早 07 : 45 按时送达 给你一份数据,你能完美的出一份数据报告吗?本文结合一个小故事,来告诉大家如何写好一份数据分析报告,enjoy~ 作者:Haby ...

  2. 论团队协作的一个小故事

    前言 因负责的工作需要复盘,工作期间出现了不少问题.为了团结同事.不破坏友好的气氛,还必须指出团队的内部问题,作为一个普通员工的我,也只能编写一个幼稚的故事来说明一些团队内部的问题. 论团队协作的一个 ...

  3. 跟涛哥一起学嵌入式 27:一个小故事,让你明白进程、线程和协程的区别

    进程.线程和协程,是多任务编程中的常用术语.很多初学者分不清它们之间的区别,今天就以一个小故事为引子,让大家搞清楚他们之间的本质区别. 话说在西凉女儿国,大唐文化传播有限公司CEO唐僧招聘了三个员工做 ...

  4. 一个小故事讲明白进程、线程、Kotlin 协程到底啥关系?

    前言 协程系列文章: 一个小故事讲明白进程.线程.Kotlin 协程到底啥关系? 少年,你可知 Kotlin 协程最初的样子? 讲真,Kotlin 协程的挂起/恢复没那么神秘(故事篇) 讲真,Kotl ...

  5. 【小知识】字符编码笔记:ASCII,Unicode 和 UTF-8

    作者: 阮一峰 http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html 前言 今天中午,我突然想搞清楚 Unicode ...

  6. 在电影里看到的一个小故事

    这个小故事好像有点安慰人的味道 但是我觉得更多的是表达一种对事的态度,毕竟生活总会有无奈的无可改变的事情 故事讲的是第一个入太空的人 他是第一个到太空的人类 俄国击败了美国,对吗? 他走上了这个大飞船 ...

  7. 一个小栗子聊聊JAVA泛型基础

    背景 周五本该是愉快的,可是今天花了一个早上查问题,为什么要花一个早上?我把原因总结为两点: 日志信息严重丢失,茫茫代码毫无头绪. 对泛型的认识不够,导致代码出现了BUG. 第一个原因可以通过以后编码 ...

  8. Windows编程01_应用程序分类,开发工具和库,第一个Windows程序,字符编码

    01 Windows应用程序分类 1.应用程序分类 控制台程序Console DOS程序,本身没有窗口,通过Windows DOS窗口执行(借的的操作系统的窗口) 窗口程序 拥有自己的窗口,可以与用户 ...

  9. 送某位经济学高才生一个小故事...

    今天有一位某位大学经济学高才生语出惊人: XXX: 我是学经济的 这个世上所有真正的诚信都是建立在不信任上 然后因为这个不信任,有了严苛的惩罚措施,然后才有了诚信... 这个也是我在小故事书上看到的 ...

最新文章

  1. java 线程池原理分析
  2. 男人一辈子就喜欢一种类型的女人,至死不渝从一而终!
  3. linux shell数组定义、元素获取及其长度获取
  4. 有赞美业微前端的落地总结
  5. python自动化测试的工具_python自动化测试(3)- 自动化框架及工具
  6. Array.from()方法
  7. java中io各种流的关闭顺序
  8. 一个有趣的IP不同的问题?
  9. 【资料整理】scribe安装配置
  10. 手把手教你MacOS如何安装SVN
  11. android播放器demo,Android 简单的本地音乐播放器Demo
  12. 流媒体直播协议与比较
  13. 对于机器学习的几点理解
  14. 微信黑名单已经删除的人怎么找回来?这些步骤挨个试试!
  15. 【Practical】最小二乘与正规方程
  16. oracle分类账设置,Oracle EBS R12 总帐和子分类账关系详解[转载]
  17. 央行:居民购房意愿仍持续上升
  18. 1309:【例1.6】回文数(Noip1999)
  19. python求一元三次方程的根_1.七年级数学:求两车多少小时后相遇?一元一次方程应用题,行程相遇问题...
  20. 邹秋实:考古的魅力在于发掘之前你不知道下面会有什么

热门文章

  1. boost::hana::permutations用法的测试程序
  2. boost::enable_current_exception用法测试程序
  3. Boost:BOOST_ASSERT_IS_VOID的测试程序
  4. DCMTK:修改DICOM文件的类
  5. DCMTK:DcmItem类的测试程序
  6. VTK:可视化之HanoiInitial
  7. OpenCV Dbt人脸检测Dbt face detection的实例(附完整代码)
  8. C语言找出两个字符串唯一不同的一个字符(附完整源码)
  9. C语言和C++语言关系
  10. netcore redis 存储集合_.net core redis的全套操作