作为一名程序员,对编码问题有必要了解一下。计算机是以二进制形式存储数据的,至于为什么选择二进制,这可能就需要从计算机的诞生历史说起了,这不是现在关注的点。
计算机文件一般可以分为(暂且这样分吧):文本文件和二进制文件。通俗的说,文本文件就是平常通过某个软件(记事本,EditPlus,UltraEdit,sublime等各种编辑器,各种IDE等等)打开看到是‘字符’,二进制文件就是打开是图片或视频等,当然是通过某些能够解释二进制的程序来完成。
这里需要强调的是,文件的后缀名不能用来区分文件的类型,后缀名只是系统(特别是Windows)用来对文件进行预先分类,并给予对应的图标和相应双击后打开的程序,至于预先给定的程序能不能打开就不得而知。
如,在Windows下,把一个txt文件的后缀名改成jpg后:
变成
双击打不开,用记事本依然能打开,就是内部编码没变,其实文件的类型在编码成二进制形式已经确定(一般是在头部)。
php读取图片文件:
$a = file_get_contents('bg.png');
echo $a;
浏览器以UTF8形式打开图片的二进制形式,虽然大部分是乱码,但头部还是可以看到解码为PNG的:
字符编码
字符编码就是每个在电脑里出现的字符都会对应一个二进制数,这个二进制可以成为码点(code point)。不同的编码形式,包含的字符数不同,字符和二进制数对应关系也不同。
ASCII码
ASCII(American Standard Code for Information Interchange)码是 比较早的编码形式,总共定义128个字符,用了一个字节的7位,也就是从 0000 0000
到 0111 1111
。
-
0-32, 127
:(共34个)不能直接显示字符的(控制或者通信使用的),32是空格也算在其中,127是删除符; -
33-64,91-96,123-126
:(共21*2=42
个)键盘上除了英文字母和不直接显示字符的键有21个,每一个键上有两个字符; -
65-90,97-122
:(26*2=56
个)英文字母;
对于英语系的人来说这128个字符已经够了,他们在电脑上接触的的字符也就这么多够了,说来也是奇特,英文字符26个就能通过组合用来表示英语所有意思,这种组合形式很适合计算机。而我们汉语就不同了,我想是不能通过笔画的组合组成所有汉字了。
据说汉语是最难学的语言之一😅,辛亏我已经会汉语了😀😝。
Unicode
128个字符对于其他语言是远远不够的,单汉字就有将近10万个(常用应该几千个),每个汉字都需要一个码点,其他语言中的字符也需要对其字符编码成码点,原本有很多编码方式,这些编码没有进行统一规定,就会有冲突,一个二进制数在不同编码方式中就有可能解释为不同的字符,这样使不同地区的人交流不便。
Unicode就是在这样条件下诞生,简单的说,Unicode就是大的映射表,把全世界所有语言符号都包含其中,每个符号的映射的码点的都不相同。 汉字大部分可以用4个16进制数表示,参看汉字Unicode。如:U+620E
表示戎
(这是我的姓,读róng,不是 戒
,在这里普及一下,我已经被叫成 戒某 无数次了🤦♀️🤦♀️🤦♀️)。
Unicode是一个庞大的字符集,一个很多字符与二进制数的唯一的一一对应集合。Unicode没有规定怎么存储,620E(0110 0010 0000 1110)
至少需要2个字节,而其他字符可能需要更多字节。像U+0041
表示英文字母A,如果也需要用2个字节或者更多,则前面的有一些字节都是0,那么太浪费资源了,为了减少空间的浪费就出现了UTF8。
UTF8
UTF8是Unicode存储一种实现方式。它采用变字节数(1-4个)来节约资源。 UTF-8的编码规则很简单,只有二条:
- 对于单字节的符号,字节的第一位设为0,后面7位为这个符号的Unicode码。因此对于英语字母,此时UTF-8编码和ASCII码是相同的。
- 对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的Unicode码。
下表总结了编码规则,字母x表示可用编码的位。
Unicode符号范围(十六进制) | UTF-8编码方式 (二进制) |
---|---|
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 |
这样解读UTF8编码就容易区分几个字节表示一个字符了。如果一个字节的第一位是0,那么这个字节单独表示一个字符;如果一个字节的第一位是1,那么这个字节下面连续几个1,就表示当前字符占用几个字节。
以汉字戎
为例,看看UTF8编码实现过程: 已知戎
的Unicode是620E
(0110 0010 0000 1110)。
-
620E
在第三行范围内(0000 0800 - 0000 FFFF)
,因此需要三个字节编码戎
,即1110xxxx 10xxxxxx 10xxxxxx
- 从
戎
的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。得戎
的UTF8编码的十六进制是E6888E
。