pupuk / blog

My New Blog. Record & Share. Focus on PHP, MySQL, Javascript and Golang.
MIT License
9 stars 2 forks source link

再回顾字符集与编码 #19

Open pupuk opened 5 years ago

pupuk commented 5 years ago

字符集与编码

Unicode

计算机内部,无论文件还是程序,归根到底,都是二进制的形式。为了表示字符,人们规定了:一个数字对应一个字符,比如32对应空格,97对应小写字母a。

我们人为规定的这个 {数字码点 <=> 字符}的集合,称之为字符集,现在最被大家认可的是Unicode字符集,当然在这之前,还有gbk, big5,等很多字符集。

例如js代码,字母a的码点是97

'a'.codePointAt()
97
'a'.codePointAt().toString(16)
"61" //16进制形式 0x61=97(十进制)

同理,根据码点可以找到对应的字符

String.fromCodePoint(65)
"A"

0xFFFF(65535)之内的汉字

'蒲'.codePointAt()
33970
String.fromCodePoint(33970)
"蒲"

编码 & utf8编码

字母'a'的码点是97,我们用1个字节byte就能保存或传输 汉字'蒲'的码点是33970,我们至少需要2字节才能保存者传输

如果一片文章,既有汉字也有字母,而且夹杂着,比如
(a)(b)(-)(-)(c) //--表示一个汉字,a,b,c是字母
程序不知道何时开始是汉字,何时开始是字母,很可能会解读成
ab连着当成一个汉字,--被解读成2个字母,就会形成乱码:
?xyc

怎么解决这个问题呢?

1、最简单的思路,用另外一个字节来标记,接下来是1个字节的字符,还是2个字节的字符,比如:

1a1b2--1c 但是可能本来100字节的内存,保存下来就100多个字节了,浪费空间,而且也不方便计算,所以计算机行业基本没有采取这种方式。

2、无论字母和汉字,都采用2个字节来传输保存,比如:

字母a,ox 0061 = 00000000 01100001 汉字蒲,0x 84b2 = 10000100 10110010 这样程序只需要2个字节依次读取,解码,就能获得正确的码点,从而找到对应的字符。程序处理简单,但是浪费了存储空间,传输也增加了多余的字节。js程序,对于字符串的处理,就是采用2个字节来对待的,虽然占用了更多的内存空间,但是计算方便,更加快速。所以这种方式在计算机,内存中,特别是高频的字符运算中还是有适用场景的。

3、变长字节编码

例如utf8,就是一套以8位为一个编码单位的可变长编码。会将一个码位编码为 1 到 4 个字节

0x 0000 - 0x 007F: 0XXXXXXX
0x 0080 - 0x 07FF: 110XXXXX 10XXXXXX
0x 0800 - 0x FFFF: 1110XXXX 10XXXXXX 10XXXXXX
0x10000 - 0x1FFFF: 11110XXX 10XXXXXX 10XXXXXX 10XXXXXX

这样字母a还是占1个字节,而且utf8只需要一个字节就能与ascii完全兼容,这是很好的一个特性。 汉字则占用3个字节,变成了0x E8 92 B2,虽然由本来的2个字节变成了3个字节。但是utf8编码,可以通过高位的1的个数,直接来知道后面几个字节是连续的。 比如汉字的utf8编码 11101000 10010010 10110010 中的第一个字节,高位有3个1,表示这是一个3个字节对应的字符。 utf8这种变长编码,兼有节省空间和便于计算的优点,在存储文件和传输中应用十分普遍,现在很多文本,代码都是以utf8保存的,很多网页在网络上传输也实用的是utf8。

utf8和码点之间的转换,可以使用下面的网站 http://www.mytju.com/classcode/tools/encode_utf8.asp

pupuk commented 5 years ago

例子:汉字unicode怎么编码成utf8的码点是33970,对应的是二进制是: 10000100 10110010 对应的十六进制是: 0x84b2 根据utf8的编码规则: 0x84b20x0800 - 0xFFFF之间,转成utf8需要3个字节

1110XXXX 10XXXXXX 10XXXXXX,其中xxxxx的位数是16位,是留给蒲的码点的二进制的,我们把蒲的码点的二进制从左往右,依次填充,得到: 11101000 10010010 10110010 这三个字节对应的16进制是: e8 92 b2 所以汉字unicode转换成`utf8是:

Unicode蒲(33970 = 0x84b2) ->utf8蒲(0xe8 92 b2)

下图是新建一个文件来验证: image 最后的 0a0x0a,ASCii码是10,对应的字符是换行符(LF NL line feed, new line),\n 因为vim编辑软件,会在文件的末尾自动加上一个\n

pupuk commented 4 years ago

golang中字符串验证

image

image 其中bt变量是个byte slice,其中[232 146 178] 共三个字节,每个字节是10进制显示。 [232 146 178] 对应的16进制是: [e8 92 b2] ,说明goalng里面保存字符串也是用utf8来保存的。

pupuk commented 4 years ago

image image