Coding中的编码问题之入门&概览

[系列文章]上一篇:《借Qt中文乱码谈谈Coding中的编码问题》 (可选择性阅读)
[系列文章]下一篇:《Coding中的编码问题之系统学习》

  大家在看完《借Qt中文乱码谈谈Coding中的编码问题》后,估计对文章中提到的很多概念性的东西似懂非懂,本系列接下来的几篇文章会陆续为大家解释清楚 (没看过该文章也无所谓啦,hhhhhh)。由于本人也是通过零散阅读包括 开源中国《字符集编码系列》等博文慢慢理解的,故本文及后续文章均会以阅读笔记的形式提供,会涵盖个人笔记以及个人小结,希望能对大家有所帮助。也方便日后自己回顾、学习。
  本文借《学习程序设计的必要准备——稀里糊涂的说说各种字符编码到底是个怎么回事。》为大家说说字符编码的大概起源和字符编码的大概发展,会再一次提及上一篇文章提到的几个Key Word (ASCII, GB2312, Big5, GBK, Unicode, UTF-8…),希望能帮大家对字符编码有一个大概的认识,为下一步系统学习打下基础。

稀里糊涂的说说各种字符编码

  原文地址:http://my.oschina.net/goldenshaw/blog/304493
  [红色部分]为标注笔记

 说起字符编码,相信做过一些开发的人都会接触过不止一种的字符编码,比如 ASCIIGB2312Big5UnicodeUTF-8,各种各样的编码常常把我们搞得不知所措,一不小心就会出现乱码问题,
 计算机这东西本质上是用来表示人类思维,帮助人类解决问题的一种机器,然而它是怎么表示人类思维的呢?我们知道计算机所能表示的只有 2 进制数字(靠高低电平实现),而我们人类的思维却是丰富多彩的,如何用简单的 2 进制数字来表示人类思维似乎成了一个不得不解决的复杂问题,人类思维这个话题是在太大了,我们很难对其做准确的定位,表示人类思维的问题就变得含糊不清,我们不妨把问题简单化,如何用计算机的 2 进制数据表示人类所应用的语言和字符呢?这样问题似乎变得简单了许多,我们知道二进制数据与十进制数据是可以互相转换的,也就说二进制数据与十进制数据本质上是没什么区别的,而十进制数据才是我们人类所熟悉的数据,在后面的内容中,我们常常会用十进制来代替二进制来进行说明(但是计算机内部可全部是由二进制表示的哦,我们用十进制只是为了说明起来方便,不要搞混了)。
 作者借 计算机如何表示人类思维 做引入,引出 如何用计算机的 2 进制数据表示人类所应用的字符呢?,这个实际上就是 字符编码

 接下来我们要进入编码的内部了,首先让我们举一个编码的简单例子,你知道 “520” 代表什么意思吗?呵呵,反应很快嘛,对的,“我爱你”,我们在网络上常常会用一些简单的数字来简化的表示一些词语,比如 “520” 代表“我爱你”,这其实就是一种简单的编码方式,“5” 代表 “我”“2” 代表 “爱”“0” 代表 “你”,这就是编码,没什么神奇的,以后无论多复杂的编码方式,其目的不过就像 “520” 代表 “我爱你” 一样, 用特定的数字来代表特定的字符,建立数字与字符间的对应关系。 看到这也许聪明的你已经有了用计算机表示人类字符的解决办法了,既然计算机只能表示数字,那我们只需要 将我们平时所用的字符一一对应成数字,在计算机内储存一大堆数字,到时候把这堆数字读出来,然后按照我们所规定的对应关系重新对应成字符 不就 ok 了吗?是的,你想的没错,我们现在的计算机正是这么干的。
 “将我们平时所用的字符一一对应成数字,在计算机内储存一大堆数字,到时候把这堆数字读出来,然后按照我们所规定的对应关系重新对应成字符” 简明扼要为大家形象生动展现了字符编码的两个方面:
  (1)、一个是编码,如何建立这样一个字符和数字的表?这个表要收集哪些字符?最后这个数字可以按照上面形式保存在计算机里面,可能包括硬盘,也包括内存;
  (2)、另一个则是解码,怎么知道这一堆数字对应哪张表?在一起的字符他们的数字会混淆在一起吗?

 老美有一帮大牛们按照我们上面的这种想法最早的发明了一种编码叫做 ASCII,这是一个单字节的字符编码方式(用一个字节来表示各种各样的字符,当然这时候只有英文字符,和少量的格式控制字符),ASCII 规定 0-127 (我们发现其实它只用了一个字节 8 位中的 7 位)分别表示不同的数字,英文字母大写,英文字母小写,各种符号和一些计算机内部必须用到的格式控制字符,这种编码方式在早期的计算机当中使用了很久,大家似乎已经觉得我们似乎已经解决用计算机来表示人类字符的问题,然后不久问题就来了。
 随着计算机的发展,慢慢不止老美们再用计算机了,很多使用希腊字符和拉丁字符的欧洲国家也开始使用计算机,这些大佬们发现目前的计算机有着严重的问题,居然不能表示希腊字符,和拉丁字符,这是严重不能接受的,后来 ASCII 就针对这个问题对自身进行了扩充,将原来用7位表示字符扩充到了 8 位,形成了完整单字节字符,所能表示的字符范围也从 128 个扩展到了 256 个。这种编码方式又继续使用了很多年。
 注: 以后的无论哪种编码方式都必须兼容 ASCII,不然以前所有用 ASCII 编码的程序全部不能使用了
 这就是为什么代码是用英文写的的原因了。。。美国的计算机发展总是领先全球,所以他们最先想到文章开头提及的问题(中国人估计那时连计算机这种东西都还没听过吧),制订了 ASCII 编码,编码的鼻祖。具体的 ASCII 编码后面会详细介绍,知道他用一个字节,而且只用了 7 位(也就是最高位始终为 0)表示,也就是有 27=128 个字符。
 也提到了第一次扩展,那就是欧洲国家利用那一个字节剩下的空间补进去一些新的字符,当然是他们经常用的 希腊字符和拉丁字符,扩展到 28=256 个字符。虽然作者没说,我觉得这个应该是 Latin-1 编码吧,学习完下一篇文章后你就知道了。

 但是事情并没有结束,不久人们就发现,一个字节的编码远远满足不了人类的需求,我们除了使用英文字符,希腊字符,拉丁字符以外,我们还使用韩文,日文,简体中文,繁体中文。暂且不说别的,单单一个简体中文的字符量就远远超出了 256,这时候我们迫切的希望有一种编码方式,能够解决我们的问题。后来 各个国家就针对自己的语言做出了各自的编码方式,别的国家我们暂且不去管它,我们就来说说中国所用到的编码方式,最早我们提出了两种主要的编码方式:一种叫 GB2312 用来表示简体中文,一种叫 Big5 用来表示繁体中文。其中 GB2312 采用两个字节的编码方式,兼容 Ascii,能够表示 6 千多个常用字(注意不是所有的汉字,仅仅是常用字), GB2312 做了这样的规定,如果发现一个单个字节小于 128,那么它表示一个英文字符(Ascii 字符),如果发现一个单个字节大于 128,那么这个字节就和他后面的字节一起构成一个双字节字符(很多情况是汉字),这样我们就一定程度上的解决了汉字的编码方式,但显然 GB2312 有着严重的问题,缺字!缺字是个很严重的问题,为了解决这个问题,GB2312 规定了一些暂时不表示任何汉字的编码,如果你发现你所想表示的汉字在 GB2312 中没有,那么你可以利用这些空白编码自己造一个你所需要的汉字临时使用,不过这种方法显然是很麻烦的,后来到了 win95/98 的时代,人们将 GB2312进行了扩展,起名字叫 GBK,汉字扩展到了 21003 个,并且简体繁体融为一库,这时候很多不太常用的汉字也可以被表示了。
 然后问题并没有被完全解决,还有繁体中文呢,繁体中文也制定了一套编码叫做 Big5(台湾省全部在用这个编码),Big5 编码和 GB2312 编码编码方式是不同的,这就导致了一个很严重的问题,比如你在大陆用GB2312 编码写了一封 Email,这封 Email 发到台湾省,台湾省的同学们用 Big5 编码打开一看全是乱码,这种事情是很让人头疼的。类似的事情还有很多,比如日文韩文中也有很多的汉字(韩国历史甚至全部是用汉字写的),但日文韩文对汉字的编码和我们 GB2312 也是不一样的,也就是说韩国人全部用汉语写的东西发到大陆来,我们看到的还是乱码。
 还是那个梗,随着国际化进程以及计算机的发展,越来越多的国家和 People 使用计算机了,这其中中国人绝对是始作俑者,哈哈。美国人越来越 hold 不住了,而且其他国家的人也有会弄计算机的了,那就是一个乱啊,没有就自己造,所以,好多种编码啊!!!为什么说中国人是始作俑者,我觉得中华文明博大精深,汉字应该是全世界最多的,人也是最多,一用起来啊,表示不了汉字字符肯定不行。中国人也是很聪明的,没有,咱们就山寨,也来弄一个,就出现 GB2312Big5GBK 编码,这些能有自己使用的字符了,爽。
 文中也提到,随着各种编码的出现,乱码也来了。原因很简单,因为沟通、因为懒。大陆人不学繁体字,就敲自己熟悉的简体字;台湾人则坚挺繁体字,不怎么学简体字,这样一来,大陆人用 GBK 编码的信息被台湾人用 Big5 解码了(反正不是编码时用的 GBK),所以就出现乱码了。
 此外,我们开始觉得这个用数字表示字符不简单了,看看那个 GB2312,还真是巧妙;另外,也体会到兼不兼容实在很重要,要是有一种编码兼容所有编码,包括 N 个国家自己的编码,那用这种不就行了吗?有木有呀?还有就是上面也有提到的,特别要兼容 ASCII,不然可能会跪,很简单,美国人的程序是 ASCII 字符啊,他们又那么强,好多东西都是他们的,不兼容,万一用不了,怎么破。

 后来呢,中日韩一起专门搞了一个编码,叫 CJK编码,汉字在这个编码里变成了统一的编码,这样交流起来就不会有问题了,但这种问题不仅仅存在于中日韩,简体、繁体直接,这种问题在世界各国是广泛存在的。为了能统一解决这种问题,后来各大软件公司搞了一个联盟,叫做 Unicode联盟,这个联盟的思想很简单,就会创造一种全世界统一的编码,能够把全人类使用的所有字符全部包含进去,以后人类全部使用这张编码,就再也不会有什么编码方式不一致所导致的乱码问题了,这种编码就是强大的 Unicode编码。Unicode 的存在似乎解决以往我们存在的所有编码问题,然后事物的发展都是一步一步进行的,Unicode 也面临着挑战,这个挑战就是说服世界给地全部使用 Unicode编码,然而这个任务到今天为止还没有做到。打个比方,我们平常在 Visual Studio 中进行开发使用的是 Unicode编码,而我们平时使用记事本写一个文件,以保存默认使用的是 GB2312编码 方式保存的。
 还真有这种编码,Unicode 带着我们的使命来了!不过,什么,竟然需要叫大家放弃之前的编码?怎么不能兼容所有编码呀?其实这是不可能的,比如,还有很多国家还没造个编码呢?怎么兼容他,开个玩笑啦。。。而且全世界真的很多个国家。
 另外,统一看来真是很重要,或者说定一个标准真的很重要。

 Unicode编码 与以往的编码是不太一样的,以往的编码都是一个字符对应一个数字,比如 Ascii 中 A 对应数字 65,但Unicode 中,一个字符真正对应计算机中的那个数字是有好几种不同存储方法的,也就是说一个字符有好几种不同的数字表示与之对应,这样做的好处是灵活,可以提供未来字符的扩展性(据说 Unicode 编码已经将未来宇宙统一所需要的编码都考虑进去了)。
 但目前,其实我们人类所使用的字符并不是很多,目前最常见的存储方法是双字节 16 位存储方式,两个字节最多可以表示 6 万多个字符,对于我们人类目前来说是足够用的了。
 由于存在着多种编码方式,当我们看到一堆十六进制、或者二进制字节,我们怎么知道它到底是哪种编码方式呢?以前的编码在内容中没有特殊的标记告诉你它用的是什么编码,看到字节的人必须提前知道它用的是什么编码方式才能对应的了解其中的内容,而 Unicode 专门规定了一个特殊的标记 (BOM),叫做 引导符也有叫引导续的,在开头用两个特殊的字节 (FF FE),表示这个文件是用 Unicode 来进行编码的(你可以用任何二进制格式的工具打开一个 Unicode编码 的文件来检查一下,看看开头两个字节是不是 FF FE),这样我们就不用提前知道(往往我们也不可能提前知道)这个文件到底是用什么编码方式来进行编码的,在开头看到这两个字节 (FF FE) 我们就知道这是一个用 Unicode编码 的文件,让事情变得更加简单了。这种引导符只有在 Unicode 中才有,其他编码格式中都没有,所以我们平时在处理文件的时候强烈建议使用 Unicode 来进行编码。
 在我们平时所做的开发中比如 .net,所有的字符编码格式都是 UTF-8,这也是我们希望看到的,我们在平时做开发时强烈建议使用 UTF-8编码,这样可以避免很多编码上不必要的问题。至于如果我们碰到了不同的编码需要对其进行转换要怎么办,.net 中处理方法是非常方便的,利用 System.Text.Encoding 这个类来对字符进行编码解码,去 MSDN一下吧,简单易用。
 Unicode 有好几种存储方式(UTF-8 得特别关注一下才行),看来 Unicode 在努力的拯救着世界,不过,不增加编码,却增加存储方式,这不是一样乱吗?难道在做 trade-off?我们姑且相信这是真的吧。另外,Unicode 里面有个叫 BOM 的东西,应该是为了在各种 存储方式 间作区分。这个也是个办法!我用几种存储方式,我设定区分的标准,保证解码绝对能看出编码是什么。毕竟上面几种编码都没提到什么用作区分的东西,Unicode 的努力可见一斑。

 好了让我们做一个简单的总结,在上面的内容中我们稀里糊涂的说到了字符编码的大概起源,和字符编码的大概发展,从早期的 Ascii、GB2312、Big5 到后来的 GBK,CJK、Unicode,UTF-8,也稀里糊涂的说了说各个编码各自要解决的问题,我想现在聪明的你对字符编码已经有了一个大概的认识。
 孰先孰后应该大致清楚了。起源、发展也有了了解。至于为什么有这样的发展,一个是用的人多了,形形色色的字符就多,需要对编码扩容;之前一段时间出现了几乎一个国家一套编码的混乱情况,迫切需要有一个统一的标准,那么 如何囊括世界所有的字符如何最好的兼容现存的编码(主要是 ASCII)如何让大家因为这种统一的好放弃原先的编码或者自然过渡到新编码? 就是 Unicode 出现和亟需解决、正在解决的问题了。

 最后来分享一道我以前看到的面试题:有一个文本文件,用 GB2312编码,里边写了一大堆英文和中文的字符,要求写一个程序(当然不让用类似 System.Text.Encoding 这种类型),输出这个文本文件中,中文字符的个数,和英文字符的个数分别是多少?
 回顾上面关于 GB2312编码 定义的说明,下面是我想到的方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
/*
* Analysis_GB2312.cpp
*
* Created on: 2016年1月27日
* Author: Chen
*/
#include <iostream>
#include <fstream>

using namespace std;

char* readFileBytes(const char* fileName, size_t& len){
 ifstream readFile(fileName, ios::in|ios::binary);
 if(!readFile){
  printf("文件打开失败!\n");
  return NULL;
 }
 readFile.seekg(0, ios::end);
 len = readFile.tellg();
 char* bytes = new char[len];
 readFile.seekg(0, ios::beg);
 readFile.read(bytes, len);
 readFile.close();
 return bytes;
}

int main(){
 const char* fileName = "your file name";
 size_t len;
 char* bytes = readFileBytes(fileName, len);
 //printf("len=%d\n", len);
 size_t countCN, countEN, countERR, countLine;

 countCN = 0;
 countEN = 0;
 countERR = 0;
 countLine = 0;
 for(size_t i=0; i<len; ){
 /*
  * GB2312 做了这样的规定,
  * 如果发现一个单个字节小于 128(0x80),那么它表示一个英文字符(Ascii 字符),
  * 如果发现一个单个字节大于 128,那么这个字节就和他后面的字节一起构成一个双字节字符(很多情况是汉字)
  * GB2312 要求汉字的每个字节均要大于 128,可能你需要以此为下面的代码做改进!!
  */
 //printf("%02x\n", (unsigned char)bytes[i]);
 if(((unsigned char)bytes[i]) > 0x80){
  countCN++;
  i += 2;
 }else if(((unsigned char)bytes[i]) < 0x80){
  if(((unsigned char)bytes[i])==0x0d && ((unsigned char)bytes[i+1])==0x0a){
   countLine++;
   countEN += 2;
   i += 2;
  }
  else{
   countEN++;
   i++;
  }
 }else{
  countERR++;
 }
}
 printf("Summary:\n");
 printf("\t本文包含中文字符: %d\n", countCN);
 printf("\t本文包含英文字符: %d,其中换行符(2个字符): %d\n", countEN, countLine);
 printf("\t本文包含不合法字符: %d\n", countERR);

 delete []bytes;
 return 0;
}

Summary

  • 这篇文章大致阐述了字符编码的起源、发展,以及各阶段出现的编码,有些我们耳熟能详,有些我们第一次听到。但是具体怎么实现的字符编码,除 ASCIIGB2312 外,甚至没有提及;关于这两种编码的叙述也是甚少。
  • 要直击问题所在,那就是需要表示的字符越来越多,需要不断对编码进行扩容;而且使用的人形形色色,字符使用频率也大小不一,造成编码方式有点泛滥,这就带来乱码的严重问题;如何进行扩容,最好能表示所有的字符,又不对现存的编码的过渡造成灾难,也就是兼容性,是一套统一的、国际的编码的最大难点,Unicode 为此在努力,我们需要好好学习一下他是怎么做的,毕竟是国际化的产物。
  • 除了 Unicode 编码体系外,作为中国人有必要了解一下国人都干了啥,需要好好学习一下 GB2312GBK,毕竟我们生活中都在用,也很频繁遇见。
  • 这些,将在下一篇文章为大家详细阐述。
文章目录
  1. 1. 稀里糊涂的说说各种字符编码
  2. 2. Summary