从一个小故事说起字符编码。..请不要误会,本文提到的编码与中国联通和中国移动无关,但请中国联通和中国移动先做个小实验,再详细说说代码。Windows系统下,在桌面右击新建一个记事本文件,打开,输入“联通”两个汉字,按Ctrl+S保存关闭。再次双击打开,你看到了什么?奇怪,文字怎么变成乱码了?好了,再新建一个文件,这次输入“move”保存再试。奇迹般地,移动显示完美运行。好吧,故事不说了,这个有趣的现象就是说说电脑里的“编码”,然后解释为什么“联通不如移动”。谈谈字符编码的历史在计算机中,所有存储的数据都是用二进制来表示的。字母、数字和字符也不例外。计算机中最小的单位是二进制位(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开始,无论是半角英文字母还是全角汉字,都是统一字符。同时,也是统一的两个字节。UTF8Unicode是1990年制定的,1994年正式使用的,那个时代到现在已经差不多是古代了。当时由于互联网不发达,没有推广。随着互联网的发展,为了解决Unicode传输的问题,当时出现了很多UTF标准。UTF-8是Internet上使用最广泛的Unicode实现。UTF-8是每次以8位为单位传输数据,UTF-16是每次16位。UTF-8最大的特点是它是一种变长编码方式。Unicode一个汉字占2个字节,UTF-8一个汉字占3个字节。UTF-8是Unicode的一种实现方式,因为UTF8是Unicode的一种实现方式,它们是互通的,也就是说,Unicode编码可以转换成UTF8,它有一套对应的规则:Unicode符号范围(十六进制)UTF8编码(二进制)00000000-0000007F0xxxxxxx00000080-000007FF110xxxxx10xxxxxx00000800-0000FFFF1110xxxx10xxxxxx10xxxxxx00010000-0010FFFF11110xxx10xxxxxx10xxxxxx10xxxxxx可以看到对于单字节符号,字节的第一位设置为0,后7位是符号的Unicode编码。所以对于英文字母,UTF-8编码和ASCII编码是一样的(见上表第一行)。对于一个n字节的符号(n>1),第一个字节的前n位全部设为1,第n+1位设为0,后面字节的前两位全部设为10其余未提及的二进制位均为该符号的Unicode编码。有点抽象,举个例子,比如来了一个汉字,计算机怎么知道是UTF8编码的呢?因为汉字是用三个字节表示的(不要问为什么用三个字节表示,这是规律),所以第一个字节的前三位全为1,第四位置0,以下位均以10开头,因此它必须如下所示:1110xxxx10xxxxxx10xxxxxx。OK,电脑按照这个规则一看就明白了,是汉字!再举个例子,从Unicode码表中找出一个汉字对应的编码,转成UTF8试试,就用我的名字“祥”吧,它的Unicode编码是\u73a5首先,第一步转换成十六进制转二进制,其值为111001110100101,那么如何拆分二进制值呢?因为UTF8是这个字符的Unicode编码的后6位,所以我们从右往左数这6位一一对应,不足的位用0补上。这样就得到了UTF8编码字“悦”得到:111001111000111010100101作为开发者,可以使用代码实现。这里使用node.js实现转码:functiontransferToUTF8(unicode){code=[1110,10,10];让binary=unicode.toString(2);//转换为二进制码[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));//将字符串转换为二进制值returnBuffer.from(code).toString();//使用Buffer转换成汉字}console.log(transferToUTF8(0x73a5));运行结果:上面代码定义了一个传递函数,参数接收一个十六进制值,表示一个Unicode字符,在传递函数内部首先转换为二进制,根据UTF-8的规则转换为对应的UTF-8编码。最后使用node.js的Buffer最终转码成汉字。可以看到汉字“祥”已经正确输出了。以上就是对Unicode和UTF-8的转换关系的简单分析。为什么中国联通不如中国移动?故事即将讲完。说了这么多编码,现在回过头来看看联通为什么会变成乱码,因为Windows记事本默认的中文保存编码是GB2312,可以通过查询找到对应的汉字“lian”GB2312编码为uc1aa,转为二进制为1100000110101010,正好16位双字节,按8位分为两组,对应UTF8的第二种编码格式:110xxxxx10xxxxxx,所以再次打开记事本Windows扫描文件时内容,它会认为这是一个UTF-8编码的文件,而不是GB2312!此时文件内容是按照UTF-8解析的,当然有乱码。这时候可以重新存为文件,把文件格式改成GB2312保存,现在打开“联通”终于可以显示了。这个例子很极端。可以说,“联通”这个词的编码只是一个巧合,但是了解编码的细节,有助于我们快速理解开发中遇到的问题的本质并加以解决。记录在这里做笔记,和大家一起学习提高。
