疑惑为什么一个Unicode:0x4F60(对应汉字是"你")会在UTF8下占用3个字节的存储空间?下面的内容就是对此的系统解释。
Unicode
Unicode就像一个电话本,标记着字符和数字之间的映射关系。Unicode并不涉及字符是怎么在字节中表示的,它仅仅指定了字符对应的数字,仅此而已。例如希伯来文中的第一个字母א,是U+2135,字母A是U+0061。
关于Unicode的其它误解包括:Unicode支持的字符上限是65536个,Unicode字符必须占两个字节。
记住,Unicode只是一个用来映射字符和数字的标准。它对支持字符的数量没有限制,也不要求字符必须占两个、三个或者其它任意数量的字节。
Unicode编码
Unicode字符是怎样被编码成内存中的字节这是另外的话题,它是被UTF(Unicode Transformation Formats)定义的。
两个最流行的Unicode编码方案是UTF-8和UTF-16。
换句话说就是:
- Unicode定义世界每个字符的索引值。
- UTF8/UTF16实现Unicode的标准,把字符存储到存储介质中。
UTF-8
根据这张表,我们可以知道0x4F60(字符"你")是在范围(0x0800-0xFFFF),所以在UF8下需要3个字节来存储。
新建一个txt,写入字符"UTF8你"(加入UTF8是为了有个基准线查看"你"的真实字节值),然后另存为UTF8编码。用notepad++(需要装HEX-editor插件)或者Binary Viewer,查看"你"在UTF8下的16进制值。
那么字符"你"在UTF8下3个字节的值是"0xE4/0xBD/0xA0".
“0xE4”–>“11100100”.
“0xBD”–>“10111101”.
“0xA0”–>“10100000”.
查看上面UTF8的表,表给出给出每个字节 的前几个固定的二进制数。
比如3个字节的Unicode会用到这个格式:“1110xxxx 10xxxxxx 10xxxxxx"来存储字符,对应到字符"你"就是"11100100 /10111101/ 10100000”。
拿出红色标注的部分"0100 111101 100000",转换成16进制就是"0x4F60"也就对应的是Unicode字符"你"。
现在我们可以知道UTF8固定每个字节的前面几位二进制值,然后用其他的位来表示字符。但是为什么UTF8的设计者们要这样设计呢?
我想这是UTF8为了兼容ASNI所要付出的代价,请查看上表,UTF8下是完全兼容asni,也就是asni标准的下的文档,在UTF8下显示完全不是问题(因为ASNI存储字节值和UTF8是一样的)。字符都是一个一个字节存储的,UTF8肯定是一个一个字节的读取,那么UTF8怎么在完全兼容ASNI前提下,是怎么知道某个字符是需要额外字节信息的?UTF8只有固定前几位二进制来决定这个字符需要以后的几个字节,又因为为了兼容ASNI,所以额外字节也需要固定前2位"10xxxxxx",来决定这个字节值不是代表ASNI字符。ASNI的格式是“0xxxxxxx”。
UTF-16
同样的,我们把txt:"UTF8你"另存为UTF16编码(windows下unicode编码就是指UTF16)
UTF16下的每个字符需要是2个或者4个字节。
字符"UTF8"在UTF16下就是"0x55/0x0054/0x0045/0x0038",那为什么图片中是0x5500呢?这涉及到高字节序和低字节序。开头的那篇文章也有介绍。字节序仅仅就是先把字符的高位或者低位先放入存储的而已。
高字节序,高位字节被存在前面
低字节序,低位字节被存在前面
比如字符"你"“0x4F60”,第一个字节是"4F"是"高位",第二个字节是"60"是"低位".
稍微解释下为什么左边是高位,玩笑话就是想想你的银行账户当然是左面数值多才有意义啊。
那么按照"低字节序" “0x4F60"就被存储为"60 4F"拉。在intel CPU下默认是"低字节序”。
在UTF16下,存储的字节值和unicode是一一对应的。但是UTF16显示英文(asni)就浪费一个字节。所以英文国家用UTF8的编码比较多。反之其他国家用UTF16的较多。
字节顺序标记(BOM)
不知道你有没有注意到,在UTF16下的这张图,地址第0,第1位是"FF FE"
这就是BOM,通过FF FE或者FE FF来告诉解释器是那种字节序。
那么你也许会问,为什么UTF8没有字节序呢?那是因为UTF8是以字节为单位,一个一个字节读取。UTF16是以字为单位,一个一个字符(2个字节或者4个字节)读取,这样就会涉及先读取第一个或者第二个字节的情况。