什么是编码
众所周知,计算机采用二进制的计算方式,一个字节占8位(为什么是8位不是9位10位?大家可以自行探讨),不管任何数据,图片,文字,声音等等,都要转换成具体的数值,最终转换成二进制数据,计算机才能处理,这个过程就是编码,本文所说的编码只是针对文字编码的介绍。
AscII码
现代计算机技术起源于美国,编码自然也是起源于美国,AscII(American Standard Code for Information Interchange)码就是第一套通用的计算机编码,它包含了英文字符,阿拉伯数字和一些常用符号,一共128个,占一个字节,第一位补0,例如常用的大写字母A是65(01000001),小写字母a是97,所以在JAVA里我们可以这样进行大小写转换:
char a = 'a';
char b = (char) (a-32);
System.out.println(b);
字符集和编码
从字面上来理解,字符集是一个包含指定字符的集合,而编码是把文字转换成对应的数字码,确实也是这样,像AscII码,他包含了128个特定字符,并且为每个字符指定了一个序号,可以称之为字符集,编码则是像查字典一样,从字符集中查到对应的序号,再把序号按编码规则转换成具体的数值,称之为编码。AscII码比较简单,只占了一个字节,他的编码结果和码表的序号值是一样的,所以它的字符集和编码的概念区分并不严格。
GB2312/GBK
随着计算机技术的发展,AscII码只能用于拉丁文字符的限制,严重影响了非英语系国家的使用,所以,各个国家和地区分别发展出了对应本地语言的编码方式,GB2312就是中国国家标准总局制定的汉字字符集标准码,GB是国标的意思。实际上,GB2312除了中文以外,还包含了拉丁字符,希腊字符,日文,俄语等等常见字符。
GB2312占据两个字节,也就是说他能容纳的字符数量为 216一共65536个。为了便于管理,GB2312将所有字符分成了若干个96字符的组,每个组称为区,字符所在的位置称为区位,这样,每个字符都有了对应的区位码。例如,汉字"安"所在的区为第16区,位置序号是18,他的区位码就是1618,有了区位码以后怎样才能转换成编码呢? 很简单,把区位码分开,区号和位置序号分别加上 0xA0,就是对应的编码了。所以"安"的GB2312编码就是 0xA0+16 0xA0+18 = 0xB0B2。
到这里,GB2312基本介绍完了,但是还有一个问题,AscII码在GB2312里是怎么处理的?实际上,GB2312兼容了AscII码,上边提到了,GB2312编码是区位码分别加A0得到的,A的编码是1010,也就是说,GB2312的编码必定是1开头的,而AscII码则是0开头的,这样就简单了,遇到1开头的编码,则按GB2312解码,遇到0开头的则按AscII解码。
GBK是GB2312的升级版,不止包含常用汉字,还包含了繁体,部首,生僻字等等,几乎可以满足所有使用汉字的场景。
Unicode
Unicode是国际编码组织制定的可以容纳世界上所有文字和符号的字符编码方案,范围从0 - 0x10FFFF,分为17组,每个组称为一个平面(plane),每个平面包含65536个字符,实际上目前只用到了少数平面,比如最常用的基本平面(BMP),范围从0-0xFFFF.其他的从 0x010000 - 0x10FFFFFF 则称为辅助平面(SMP),实际上在我们的日常使用中BMP基本上能满足所有要求。
Unicode包含了全世界日常使用的几乎所有字符,例如汉字"安",他的Unicode码是0x5B89。
utf-8/16/32
什么是utf编码?上边介绍了Unicode,它是一个字符集,规定了每个字符对应的数值,utf则是具体的实现方式,用各种方法来存储字符对应的数值,而根据各种实现方式的不同,又分为utf-8/16/32。
utf8是变长编码,如果是1字节,它和Ascall码是一样的,多字节时第一字节的第一位开始填1,有几个1表示占几个字节,比如110x xxxx表示两个字节,第二个字节开始一直到结束,都是10开头,完整的编码如下:
2字节: 110x xxxx 10xx xxxx
3字节: 1110 xxxx 10xx xxxx 10xx xxxx
同样拿汉字"安"来说,它的Unicode码是0x5B89,转换成2进制填充到utf-8的空位里去,多余的位置补0得到结果:0xE5AE89。
5B89
转换成2进制: 0101 101110 001001
填充到utf8格式中:1110 0101 10 101110 10 001001
得到utf8码: E5AE89
现在我们再看一下字符集和编码,就比较清楚了,Unicode是字符集,utf-8编码则是该字符集的一种实现方式,他们并不是同一个概念。
再来看一下utf-16编码,utf-16的长度固定为2个或者4个字节,通常我们使用2个字节就可以了,它对应的就是Unicode中的基本平面BMP,编码就是Unicode码,不足2个字节的位数在前边补0,同样是汉字"安",它的Unicode码是0x5B89,所以utf-16码也是0x5B89。所以AscII码也会占据两个字节,会有一部分空间浪费。编码Unicode辅助平面的字符时,utf-16占用4个字节,平时几乎用不到,所以具体的编码规则就不做介绍了。
utf-32编码固定占用4个字节,他对应的是全部Unicode码,这种编码方式基本用不到,也不需要深入了解。
BOM,LE,BE
这几个词平时出现的频率比较少一些,LE表示Little Endian,小端序,BE表示Big Endian,大端序,分别代表了计算机领域数据存储的两种方式。
大端序:
数值: 0x12345678
内存低位地址(起始): 12
34
56
内存高位地址(结束): 78
小端序:
数值: 0x12345678
内存低位地址(起始): 78
56
34
内存高位地址(结束): 12
实际上我们在日常使用过程中不需要关心当前计算机使用的是那种方式。但在UTF-16编码时,它也有大小端序两种编码方式,例如0x5E89,大端序值为0x5E89,小端序则要反过来表示为0x895E,为了区分这两种方式,在采用UTF-16编码时,在数据流的开始添加了一个统一的标识,0xFEFF表示大端序,对应的编码是utf-16be,0xFFFE表示小端序,对应的编码是utf-16be,这个表示就是BOM。
BOM全称Byte Order Mark,字节序标记,除了utf-16之外,utf-8也可以添加bom,它的bom固定为0xEFBBBF,选择编码方式为utf-8 with bom时,生成的文件流中就会出现这个bom。为什么utf-8可以不需要bom呢,因为utf8是变长的,它根据第一个字节信息判断每个字符的长度,不存在正反顺序的问题,我们日常使用的utf-8都是不带bom的。
Java里的编码
看完了字符编码的介绍,这里有一个问题,java里的char类型能不能存储中文字符?
java里char占了两个字符,很容易得出结论,如果采用utf-8,是不能存储中文的,其他编码方式GB2312,utf-16都可以存储中文,是这样吗?
其实没有这么简单,不管我们在编辑器里选什么编码方式,对java里的char都没有影响,实际上char对应的是Unicode的基本平面BMP。我们在浏览器里选择的编码只是对应文件的保存方式,跟char采用什么编码没有任何关系,同样的,如果我们在new一个String的时候指定了编码,这个编码也只对这一个String生效,而Java里的char是编译器里定死了的,它对应的就是BMP,也可以认为是utf-16的2字节部分。
那么ava里的char类型到底能不能存储中文字符,当然可以,但是仅对应BMP部分,各种繁体字生僻字就无能为力了。