昨晚帮同组一个新入行的intern解决了一个中文乱码的问题,想想这个中国程序员几乎无法回避的问题,曾经引得多少程序员竞折腰!很多新人被搞的晕晕乎乎很大一部分原因是因为网上关于这个问题的“歪门邪道”的解法太多,各种场景,各种解法,有人蒙对了,问题解决了,happy ending!有人运气不好没碰上,只能抓狂了。。。
其实这个问题的根本原因不难,谁都都知道问题的关键就一点:编码字符集和解码的字符集不一致!
可是具体要找到怎么造成的不一致,这个过程往往没那么简单,你要熟悉你的字符在整个传输过程中,在哪些地方被编码了,又在哪些地方被解码了。在复杂一点的场景中,这些地方可能不止一处,甚至还会在中间参杂加解密的过程。。。
好了,上面扯了一点背景,下面会给出基于web应用的B/S架构的一些常见场景问题讨论,希望对不是很清楚整个编码过程原理的同学有所帮助。
关于字符集(CharSet)和字符编码(CharEncoding)
这个问题其实是很多人从一开始就忽略的两个概念。但是确实是字符编码最核心的两个东西。
这两个概念通常可以在网上搜到比较官方的解释,这里我只给出我个人更口语化的描述,应该更好懂一点。
字符集:是用来定义你想要表示的字符和你的对应码表的映射关系的。听起来还是有点抽象。这里拆开有几个点:首先,这里涵盖了你要表示的字符的集合;然后,这里你必须有一套码表;最后,你得定义你的码表中每一个码字是怎么和字符集合中一个特定的字符关联的。如果你对数学很敏感,那么这个字符集可以归结为一个逻辑上一元函数:y = f(x) ,当然,这个函数并不是连续的。
字符编码:用来规定上面提到那套码表中的每个码字到底怎么在计算机存储中或者网络传输中表示为字节(或者字节组合)的。
OK,如果你了解了上面提到的两个概念,我们来看看日常生活中经常接触的那些个词都是神马东西。
- ASCII:美国佬在最初创建计算机字符时,谁也没鸟,只想着英语那几个字母,加上常用的一些公式字符啊神马的,就哦了。于是他们最初很狭隘的值定义了一套只有128个元素的码表,这套码表也就顶多映射128个字符了。如果你是个程序员,那么你应该很清楚,128个元素可以轻松用一个字节搞定。于是美国佬也是这么干的,直接用一个字节搞定了ASCII。他们玩的很happy!
- ISO-8859系列:计算机传入了世界各地,传入了民用领域。大家为美国人只考虑自己的行为非常不满意,在口诛笔伐的同时,纷纷发奋图强,自力更生,一时间全球范围内出现了各个地区的造字造码运动(这其中当然包括我天朝,事实上,今天你很熟悉的GB系列的双字节编码也是那个动荡年代产生地)。尼玛,这还了得,这严重影响了全球人民的交流啊。得有全球统一的标准啊。于是身为标准组织的ISO大哥站出来,一声巨吼:“都给我消停儿地!”。于是这位哥哥不辞辛苦的搞出了ISO-8859-1~ISO-8859-15一个系列字符集。不过这个系列中,到今天还最为广大程序员熟知还是他们家族的老大ISO-8859-1。为什么呢?因为他完全和后面提到的Unicode的前256个字符码表定义重合吗?那你天真了,因为这货往往是你遇到乱码问题的罪魁祸首(作为完全兼容ASCII字符集的西欧字符集,这货基本cover第一世界国家,也是作为绝大部分的Linux/Unix发行版本的默认字符集)。注意,到此时,我们所接触的字符集都是可以用单字节搞定的。
- GB系列:这个系列中,我相信你最熟悉肯定是GBK和GB2312。事实上,GB2312才是我天朝第一个正统皇家血统的字符集。我大中华文字博大精深,岂能就用一个字节敷衍了事,必须两个字节!(其实,当我们考虑字节数量的时候,已经在不知不觉的考虑字符编码的问题了。)不过遗憾的是在GB2312的制定过程中,真正涵盖的中文汉字也没超过7000个。对于符号的涵盖也有遗漏。于是乎,GBK作为一个商业上的事实标准,闪亮登场了。他在前者的基础上扩充了若干字符,形成了更为全面的汉字库(简繁均被纳入麾下),并且做到了对GB2312的全兼容。
- Unicode:这货应该整天在你耳边萦绕,而绝大部分人除了知道他号称涵盖了当今世界上所有字符之外,却未必真正了解他。他是专门的一个Unicode的一个组织创建和维护的(是的,你没有看错,他出身跟ISO没有半毛钱关系!)。话说ISO也不是吃闲饭的,当然也意识到了全球大一统的重要性。所以几乎在同时期,ISO也有和Unicode非常同志的字符集UCS-2和UCS-4(简单粗暴的区分的话,可以认为2表示需要两个字节,4需要4个字节范围来涵盖对应的码表;虽然事实上并非如此简单)。我相信这两个字符集对于绝大部分人都是陌生的。不过很快两个组织就认识到了两种标准将给未来带来的麻烦,所以今天事实上的Unicode字符集基本上和UCS-2是完全兼容的。说到这里,你终于接触了一个纯字符集。Unicode本身其实并没有任何具体存储的实现细节规定。
- UTF系列:这个系列“基本”可以认为是针对Unicode不同字符编码实现了。这个家族中你最常看见的基本上就是UTF-8/16/32这几个了。UTF-16作为最简单粗暴的Unicode实现,直接使用两个字节对Unicode的码表进行映射。这种实现看似简单,但在真正传输或者存储的过程中,其实还有个问题得明确,就是这两个字节谁前谁后?这个问题涉及到所谓Big-Endian或者Little-Endian的选择上,通常这个和乱码问题不会扯上直接的关系,所以这里暂时略过。对于UTF-32更准确的说是对应上面提到的UCS-4的字符编码实现,所以平时基本接触不到。UTF-8才是真正意义上的Web常用字符编码之一,而且也是因为他的特殊变长(他实际上的变长范围是1~6个字节,网上绝大部分的中文资料只写了是1~4个字节)规则,导致他其实不需要前面提到Big-Endian还是Little-Endian的问题。
擦,发现扯了一大通,居然还为进入乱码主题,太标题党了。
想把这个说清楚也着实不易啊,今天先到这里吧,只能先把标题加个(一)了。
未完待续。。。