我们大部分人真正意识到有一个字符编码,通常是因为出现乱码,因为我国常用的编码有GBK和GB2312:用两个Bytes表示的所有汉字,在这样,我们总共可以表示2^16=65536个字符。一旦我们的GBK、GB2312编码遇到其他编码,比如日文、韩文编码,就会变成乱码。当然如果此时是UTF-8,也是乱码。我们知道在计算机内部,为了将二进制数据转换成显示,需要进行编码,即将可以显示的字符一一映射到二进制数据,比如ASCII码,就是使用一个字节数据来表示英文字符加上一些英文符号。至于中文,我们显然不能只用一个Byte来表示,我们需要使用更多的空间。Unicode和Codepoint在今天的小世界村里有那么多的语言和文字。为了兼容所有的字符,出现了Unicode,但是它需要更多的字节来容纳世界上所有的字符(这甚至包括Emoji)。要想理解Unicode,就需要理解Codepoint,所谓代码点,即用4个Byte大小的数字来表示所有的字符。至于Unicode本身,你可以认为它是一个Code点的集合,那么UTF-8呢?是Unicode的编码方式。Unicode与UTF-8编码下图来自UTF-8的截图:这张图简单明了的告诉我们UTF-8的编码方式,比如汉字一般使用三个Bytes,每个Byte的开头是固定的。各种文本软件在解析UTF-8编码时,都会按照这种格式进行解析。一旦解析错误(毕竟可能有不符合要求的数据,或者文件错误),错误的字节会被替换为“?”(U+FFFD),然后神奇的事情发生了:偶如果遇到这种错误,不会影响其他字符的解析,因为这种编码不必从头开始,所以它可以自同步(Self-synchronizing)。同时,还有一些其他的代码一旦遇到错码就会出错,导致错码后正确的代码出错。当然UTF-8编码也有缺点。因为是可变的,当英文字符过多时,会节省空间。但当汉字过多时,理论上(3Byte)比GBK编码(2Byte)最多多1/3的存储空间。UTF-8示例我们以Unicode中最流行的Emoji:joy:1为例:它的Codepoint是U+1F602(是的,1F602是用十六进制表示的),但是它在内存中的存储方式是0xf09f9882,为什么?这是UTF-8编码(注意对比上图的编码方式):0000111110110000000101f60211110000100111111001100010000010f09f9882通过提取UTF-8编码网格中的数据,我们可以得到1F60码位2。也可以使用Golang来检查编码其他字符:packagemainimport("fmt""unicode/utf8")funcmain(){fmt.Printf("%b\n",[]byte(`:joy:`))fmt.Printf("%x\n",[]byte(`:joy:`))r,_:=utf8.DecodeRuneInString(`:joy:`)fmt.Printf("%b\n",r)fmt.Printf("%x\n",r)}Unicode的其他编码Unicode当然不止一种编码,还有UTF-16、UTF-32等,它们的关系是UTF-16用2个Bytes表示UTF-8个字符用1/表示分别是2/3Byte,然后4Byte与UTF-8一致,UTF-32用4Byte来表示所有字符,另外,具体可以看Unicode编码的比较中可以看到,嗯,基础知识说完了,下面开始正式介绍。Unicode与Golang2这里需要提到的是Golang与UTF-8的关系。他们背后的人是KenThompson和RobPike345。由此大家就会明白Golang的UTF-8设计是独一无二的。多么重要的参考。例如,Golang设计了一个rune类型来代替Codepoint的含义。rune看源码就知道是int32,刚好是4个Bytes,正好可以用来表示所有Unicode编码UTF-8和UTF-16。在继续之前,我想帮助大家了解一个事实:Golang的源代码默认是UTF-8编码的,从我上面举的例子就可以理解,所以emoji字符在编译的时候就已经可以解析了。好吧,我们来看看Golang的unicode包,里面有很多有用的判断函数:boolfuncIsPunct(rrune)boolfuncIsSpace(rrune)boolfuncIsSymbol(rrune)boolfuncIsTitle(rrune)boolfuncIsUpper(rrune)bool另外,在src/unicode/tables.go中,Unicode区间的各种字符有大量的码位,会有具有比较大的参考价值。再看看unicode/utf8包。这个包中的大部分函数你用不到,但是有几类情况你需要用到它们:计算字符数;转码,如GBK转UTF-8;判断字符串是否为UTF-8编码,或者是否包含不符合UTF-8编码的字符;后面两个可以忽略,第一个需要特别提醒:s:=`:joy:`fmt.Println(len(s)的输出是什么)?如上所述,它恰好是4。所以不能用len来获取字符数,也不能用它来判断用户输入的字符是否超过系统限制。另外,你不能通过s[0]得到字符,因为你只能得到这4个Bytes的第一个,也就是0xf0。你应该做的是将字符串转换成符文数组,然后进行字符操作。我不会详细介绍如何使用它,但我相信你可以处理它。另外,这里还需要再提醒一下。在Node.js中,string本身是Unicode,而不是像Golang的string那样是二进制的。因此,可以认为Node.js的Buffer就是Golang中的字符串。好吧,我留给你最后一个思考问题:在Node.js中,为什么在处理Buffer时不能直接连接?
