彻底弄懂UTF-8、Unicode、宽字符、locale

最近使用到了wchar_t类型,所以准备详细探究下,没想到水还挺深,网上的资料大多都是复制粘贴,只有个结论,也没个验证过程 。本文记录探究的过程及结论,如有不对请指正 。
Unicode、UCS
UCS(Universal Character Set)本质上就是一个字符集 。
Unicode的开发结合了国际标准化组织所制定的 ISO/IEC 10646,即通用字符集(
Universal Character Set, UCS) 。Unicode 与 ISO/IEC 10646 在编码的运作原理相同,但 The Unicode Standard 包含了更详尽的实现信息、涵盖了更细节的主题,诸如比特编码(bitwise encoding)、校对以及呈现等 。摘自(Unicode)
所以也可以简单的理解为,Unicode和UCS等价,都是字符集 。
UCS编码的长度是31位,可用4个字节表示,可以表示2的31次方个字符 。如果两个字符的高位相同,只有低16位不同,则它们属于同一平面,所以一个平面由2的16次方个字符组成 。目前大部分字符都位于第一个平面称为BMP 。BMP的编码通常以U+xxxx这种形式表示,其中x是16进制数 。
比如中文“你”对应的UCS编码为U+4f60,“好”对应的UCS编码为U+597d 。更多中文编码可以在Unicode编码表中查询 。
有了UCS编码,任何一个字符在计算机中都最多可以用四个字节来表示,称为码点 。
UTF8
现在有了UCS字符集,那么一个字符在计算机中真的要按四个字节(UTF-32)来存储吗?
答案是否定的,一方面每个字符都按四字节来存储非常浪费空间,因为大部分字符都在BMP,只有后16位有效,前16位都是0 。另一方面这与C语言不兼容,在c语言中0字节表示字符串的结尾,库函数strlen等函数依赖这一点,如果按UTF-32存储,其中有很多0字节并不表示字符串结尾 。
Ken Thompson发明了UTF-8编码,可以很好的解决以上问题 。Unicode 和 UTF-8 之间的转换关系表如下:
码点起值码点终值字节序列Byte1Byte2Byte3Byte4Byte5Byte6U+0000U+007F10xxxxxxxU+0080U+07FF2110xxxxx10xxxxxxU+0800U+FFFF31110xxxx10xxxxxx10xxxxxxU+10000U+1FFFFF411110xxx10xxxxxx10xxxxxx10xxxxxxU+200000U+3FFFFFF5111110xx10xxxxxx10xxxxxx10xxxxxx10xxxxxxU+4000000U+7FFFFFFF61111110x10xxxxxx10xxxxxx10xxxxxx10xxxxxx10xxxxxx
第一个字节要么最高位是0(ASCII码),要么最高位都是1,最高位之后的1的个数决定了后面的有多少个字节也属于当前字符编码,例如111110xx,最高位之后还有4个1,表示后面的4个字节属于当前编码 。后面的每个字节的最高位都是10,可以和第一个字节区分开来 。后面字节的x表示的就是UCS编码 。所以UTF-8就像一列火车,第一个字节是车头,包含了后面的哪几个字节也属于当前这列火车的信息,后面的字节是车厢,其中承载着UCS编码 。
以中文字符“你”为例,对应的Unicode为"U+4f60",二进制表示为0100 1111 0110 0000 。按照表中的规则编码成UTF-8就是11100100 10111101 10100000(0xe4 0xbd 0xa0) 。
结论:
Unicode本质是字符集,在这个集合中的任意一个字符都可以用一个四字节来表示 。
UTF-8是编码规则,可以通过这个规则将Unicode字符集中任一字符对应的字节转换为另一个字节序列 。UTF-8只是编码规则中的一种,其它的编码规则还有UTF-16,UTF-32等 。
宽字符类型wchar_t
在介绍宽字符前先了解下locale 。因为多字节字符串和宽字符串的转换和locale相关 。
locale
什么是locale
区域设置(locale),也称作“本地化策略集”、“本地环境”,是表达程序用户地区方面的软件设定 。在linux执行locale可以查看当前locale设置:
ubuntu@VM-0-16-ubuntu:~$ localeLANG=zh_CN.UTF-8LANGUAGE=LC_CTYPE="zh_CN.UTF-8"LC_NUMERIC="zh_CN.UTF-8"LC_TIME="zh_CN.UTF-8"LC_COLLATE="zh_CN.UTF-8"LC_MONETARY="zh_CN.UTF-8"LC_MESSAGES="zh_CN.UTF-8"LC_PAPER="zh_CN.UTF-8"LC_NAME="zh_CN.UTF-8"LC_ADDRESS="zh_CN.UTF-8"LC_TELEPHONE="zh_CN.UTF-8"LC_MEASUREMENT="zh_CN.UTF-8"LC_IDENTIFICATION="zh_CN.UTF-8"LC_ALL=可以将locale理解为一系列环境变量 。locale环境变量值的格式为language_area.charset 。languag表示语言,例如英语或中文;area表示使用该语言的地区,例如美国或者中国大陆;charset表示字符集编码,例如UTF-8或者GBK 。
这些环境变量会对日期格式,数字格式,货币格式,字符处理等多个方面产生影响 。
参考资料:

  1. locale wiki
  2. Environment Variables
如何设置系统默认的locale
修改配置文件/etc/default/locale,比如要将locale设为zh_CN.UTF-8,添加如下语句LANG=zh_CN.UTF-8


推荐阅读