编码和解码可以理解成二进制和字符(广义的字符,包括汉字等)的映射表,编码即从字符映射至二进制,解码则为逆过程。
1.英语字符编码ASCII
开始计算机只在美国用。8字节一共可以组合出256(2的8次方)种不同的状态。美国人把其中的编号从0开始的32种状态分别规定了特殊的用途,一但终端、打印机遇上约定好的这些字节被传过来时,就要做一些约定的动作。遇上00x10, 终端就换行,遇上0x07, 终端就向人们嘟嘟叫,例好遇上0x1b, 打印机就打印反白的字,或者终端就用彩色显示字母。他们看到这样很好,于是就把这些0x20以下的字节状态称为"控制码"。
他们又把所有的空格、标点符号、数字、大小写字母分别用连续的字节状态表示,一直编到了第127号,这样计算机就可以用不同字节来存储英语的文字了。大家看到这样,都感觉很好,于是大家都把这个方案叫做 ANSI 的"ASCII"编码(American Standard Code for Information Interchange,美国信息互换标准代码)。当时世界上所有的计算机都用同样的ASCII方案来保存英文文字。
随着世界各地的都开始使用计算机,但是很多国家用的不是英文,他们的字母里有许多是ASCII里没有的,为了可以在计算机保存他们的文字,他们决定采用127号之后的空位来表示这些新的字母、符号,还加入了很多画表格时需要用下到的横线、竖线、交叉等形状,一直把序号编到了最后一个状态255。从128到255这一页的字符集被称"扩展字符集"。
简而言之,ASCII码一共规定了128个字符的编码,比如空格“SPACE”是32(二进制00100000),大写的字母A是65(二进制01000001)。这128个符号(包括32个不能打印出来的控制符号),只占用了一个字节的后面7位,最前面的1位统一规定为0。
2.中文编码GB2312、GBK、GB18030
GB2312:GB2312 是对 ASCII 的中文扩展。中国人得到计算机时,已经没有可以利用的字节状态来表示汉字,况且有6000多个常用汉字需要编码。我们规定:一个小于127的字符的意义与原来相同(同ASCII),但两个大于127的字节连在一起时,就表示一个汉字,其中高字节范围规定为从0xA1—0xF7,低字节范围规定为从0xA1—0xFE。这套编码中,不仅包括了7000多个常用简体汉字,还包括数学符号、罗马希腊字母、日文假名。 ASCII 里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,即所谓的"全角"字符,而原来在127号以下的那些就叫"半角"字符了。GB2312使用两个字节表示一个汉字,所以理论上最多可以表示256x256=65536个符号。
GBK 标准:由于中国仍旧有许多偏僻字无法表示。后来规定:只要第一个字节是大于127就固定表示这是一个汉字的开始,不管后面跟的是不是扩展字符集里的内容。即形成GBK标准,GBK 包括了 GB2312 的所有编码,同时又增加了近20000个新的汉字(包括繁体字)和符号。GBK标准包括拉丁字母、汉字、日文、韩文、俄文,不包括俄语、葡萄牙语。
GB18030标准:扩展至少数民族字母符号。
这一系列标准称为 "DBCS"(Double Byte Charecter Set 双字节字符集)。其最大的特点是2字节长的汉字字符和1字节长的英文字符并存于同一套编码方案里。以前的程序程序为了支持中文处理,必须要注意字串里的每一个字节的值,如果这个值是大于127的,那么就认为一个双字节字符集里的字符出现了。注意,GB类的汉字编码与后文的Unicode和UTF-8是毫无关系。
3.统一码UNICODE
各个国家都像中国这样搞出一套自己的编码标准,结果互相之间谁也不懂谁的编码,谁也不支持别人的编码。即,尽管二进制相同,由于映射表不一样,换成他人的标准解出来就是乱码。虽然英语用128个符号编码就够了,但是用来表示其他语言,128个符号是不够的。比如,在法语中,字母上方有注音符号,它就无法用ASCII码表示。于是,一些欧洲国家就决定,利用字节中闲置的最高位编入新的符号。比如,法语中的é的编码为130(二进制10000010)。这样一来,这些欧洲国家使用的编码体系,可以表示最多256个符号。但是,这里又出现了新的问题。不同的国家有不同的字母,因此,哪怕它们都使用256个符号的编码方式,代表的字母却不一样。比如,130在法语编码中代表了é,在希伯来语编码中却代表了字母Gimel (ג),在俄语编码中又会代表另一个符号。
ISO 解决了这个问题。他们采用的方法很简单:废了所有的地区性编码方案,重新搞一个包括了地球上所有文化、所有字母和符号的编码!这种它"Universal Multiple-Octet Coded Character Set",简称 UCS, 俗称 "UNICODE"。
UNICODE 开始制订时,计算机的存储器容量极大地发展了,空间再也不成为问题了。于是 ISO 就直接规定必须用两个字节,也就是16位来统一表示所有的字符。对于ASCII里的那些“半角”字符(1-127),UNICODE 包持其原编码不变,只是将其长度由原来的8位扩展为16位,而其他文化和语言的字符则全部重新统一编码。由于"半角"英文符号只需要用到低8位,所以其高8位永远是0。因此这种大气的方案在保存英文文本时会多浪费一倍的空间。
但是,UNICODE 在制订时没有考虑与全球任何一种现有的编码方案保持兼容,这使得 GBK 与UNICODE 在汉字的内码编排上完全是不一样的。但由于其对于所有语言的通用性,得到了迅速推广,比如MS 和WINDOWS 操作系统。
Unicode是一个很大的集合,现在的规模可以容纳100多万个符号。每个符号的编码都不一样,比如,U+0639表示阿拉伯字母Ain,U+0041表示英语的大写字母A,U+4E25表示汉字“严”。
4.UNICODE传输标准之UTF-8
一般而言,UNICODE 用两个字节来表示为一个字符,他总共可以组合出65535不同的字符,这大概已经可以覆盖世界上所有文化的符号。如果还不够也没有关系,ISO已经准备了UCS-4方案,说简单了就是四个字节来表示一个字符,可以组合出21亿个不同的字符。
但是注意到Unicode只是一个符号集(映射表),它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。比如,汉字“严”的unicode是4E25(100111000100101)表示至少需要2个字节。这里就有两个严重的问题:
1.如何才能区别unicode和ascii?计算机怎么知道三个字节表示1个符号而不是分别表示3个符号
2.存储浪费。英文字母只用一个字节表示就够了,unicode可能会使用2个以上的字节表示英文字母,大量的0浪费了存储。
在互联网普及的背景下,出现了传输unicode的UTF标准(Unicode Transformation Format)。例如普遍使用的UTF-8(Unicode Transformation Format -8 bit)就是每次8个位传输数据,而UTF-16就是每次16个位。为了传输时的可靠性,从UNICODE到UTF要过一些算法和规则来转换。Unicode码转换成UTF-8传输码规则如下:
Unicode符号范围 | UTF-8编码方式
(十六进制) | (二进制)
--------------------+---------------------------------------------
0000 0000-0000 007F | 0xxxxxxx
0000 0080-0000 07FF | 110xxxxx 10xxxxxx
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
举例而言 "汉"字的Unicode编码是6C49(范围0800-FFFF,二进制0110 1100 0100 1001),使用模板:1110xxxx 10xxxxxx 10xxxxxx转成UTF-8为E6 B1 89(1110-0110 10-110001 10-001001)。同样,汉字“严”的unicode是4E25(100111000100101),对应的UTF-8编码是E4B8A5(“11100100 10111000 10100101”)。
例子:在 windows 的记事本里新建一个文件,输入"联通"两个字之后,保存,关闭,然后再次打开,你会发现这两个字已经消失了,代之的是几个乱码!
其实这是GB2312编码与UTF8编码冲突导致的。新建一个文本文件时,记事本的编码默认是ANSI,如果你在ANSI的编码下输入汉字,那么他实际使用GB系列的汉字编码方式,在这种编码下,"联通"的二进制码是C1AA (1100 0001 1010 1010),CDA8(1100 1101 1010 1000)。注意到,“联通”的GB编码都符合UTF-8的第二种模板("110***** 10******"),再次打开记事本时,记事本就误认为这是一个UTF-8编码的文件。会将其转回统一码显示,其统一码Unicode为 006A 0368("0000 0000 0110 1010"),前者是小写的字母"j",而后者什么也不是。这就是显示为乱码的原因。而如果你在"联通"之后多输入几个字,其他的字的编码不见得又恰好是110和10开始的字节,这样再次打开时,记事本就不会坚持这是一个UTF-8编码的文件,而会用ANSI的方式解读,这时乱码又不出现了。
5.Little endian和Big endian的区别与识别
这两个古怪的名称来自英国作家斯威夫特的《格列佛游记》。在该书中,小人国里爆发了内战,战争起因是人们争论,吃鸡蛋时究竟是从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开。为了这件事情,前后爆发了六次战争,一个皇帝送了命,另一个皇帝丢了王位。
在网络里传递信息时有一个很重要的问题,就是对于数据高低位的解读方式,一些计算机是采用低位先发送的方法,例如我们PC机采用的 INTEL 架构,而另一些是采用高位先发送的方式,如网络中交换数据。为了便于传输,采用标志符的方法。具体而言二者区别如下。
Big-Endian俗称大头存储:内存从最低地址开始按顺序存放,低位地址存放数据高位字节。UFT 格式文本流传送标志位FEFF
Little-Endian俗称小头存储:内存从最低地址开始按顺序存放,低位地址存放数据低位字节。UFT格式文本流传送标志位FFFE
6.中文window操作系统编码总结
1)ANSI是默认的编码方式。对于英文文件是ASCII编码,对于简体中文文件是GB2312编码(只针对Windows简体中文版,如果是繁体中文版会采用Big5码)。英文字母占1个字节,汉字占2个字节
2)Unicode编码(默认的little endian)具体指UCS-2编码方式,即直接用2个字节的Unicode码。英文和汉字都是2个字节存储。
3)Unicode big endian编码与上一个选项相对应。英文和汉字都是2个字节存储。
4)UTF-8编码,也就是上一节谈到的编码方法。一个英文字母占1个字节,一个汉字占3个字节
一般而言,ANSI占用存储空间最小,也就是为什么默认使用的原因。
7.编码的相互转换
public class Tranform {
public static void main(String args[]) throws Exception {
String chineseStr="我们";
Transform t=new Transform();
System.out.println(t.Chinese2UTF_8(chineseStr));
System.out.println(t.Chinese2GBK(chineseStr));
System.out.println(t.GBK2Chinese(t.Chinese2GBK(chineseStr)));
}
}
class Transform {
//中文转换成UTF-8编码(16进制字符串),每个汉字3个字节
public String Chinese2UTF_8(String chineseStr)throws Exception {
StringBuffer utf8Str = new StringBuffer();
byte[] utf8Decode = chineseStr.getBytes("utf-8");
for (byte b : utf8Decode)
utf8Str.append(Integer.toHexString(b&0xFF));
return utf8Str.toString().toUpperCase();
}
//中文转换成GBK码(16进制字符串),每个汉字2个字节
public String Chinese2GBK(String chineseStr)throws Exception {
StringBuffer GBKStr = new StringBuffer();
byte[] GBKDecode = chineseStr.getBytes("gbk");
for (byte b : GBKDecode)
GBKStr.append(Integer.toHexString(b&0xFF));
return GBKStr.toString().toUpperCase();
}
//16进制GBK字符串转换成中文
public String GBK2Chinese(String GBKStr)throws Exception{
byte[] b = HexString2Bytes(GBKStr);
String chineseStr = new String(b, "gbk");//输入参数为字节数组
return chineseStr;
}
//把16进制字符串转换成字节数组
public byte[] HexString2Bytes(String hexStr) {
byte[] b = new byte[hexStr.length() / 2];
for (int i = 0; i < b.length; i++)
b[i]=(byte) Integer.parseInt(hexStr.substring(2*i,2*i+2),16);
return b;
}
//把字节数组转换成16进制字符串
public static final String bytesToHexString(byte[] byteArray){
StringBuffer hexStr = new StringBuffer(byteArray.length*2);
for (int i = 0; i < byteArray.length; i++) {
String sTemp= Integer.toHexString(0xFF& byteArray[i]);
int j=0;
while(j<2-sTemp.length())
{sTemp="0"+sTemp;j++;}
hexStr.append(sTemp.toUpperCase());
}
return hexStr.toString();
}
}
注意:
str.getBytes("UTF-8"); 的意思是以UTF-8的编码取得字节
new String(XXX,"UTF-8"); 的意思是以UTF-8的编码生成字符串
public static String toUtf_8(String str) {
return new String(str.getBytes("UTF-8"),"UTF-8");//字符串转成utf8
}
本文参考:
1.http://blog.csdn.net/iefreer/article/details/4836844
2.http://bbs.csdn.net/topics/360164725
3.http://www.hackbase.com/tech/2009-03-16/51640_1.html
4.http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html
5. 各种编码UNICODE、UTF-8、ANSI、ASCII、GB2312、GBK详解