聊聊计算机中的编码(Unicode,GBK,ASCII,utf8,utf16,ISO885。。。
作为⼀个程序员,⼀个中国的程序员,想来“乱码”问题基本上都遇到过,也为之头疼过。出现乱码问题的根本原因是编码与解码使⽤了不同⽽且不兼容的“标准”,在国内⼀般出现在中⽂的编解码过程中。
我们平时常见的编码有Unicode,GBK,ASCII,utf8,utf16,ISO8859-1等,弄清这些编码之间的关系,就不难理解“乱码”出现的原因以及解决办法。
所谓字符集编码其实就是将字符(包括英⽂字符、特殊符号,控制字符,数字,汉⼦等)与计算机中的⼀个数字(⼆进制存储)⼀⼀对应起来,⽤这个数字来表⽰该字符,存储该字符的时候就存储这个数字。⽐如a对应数字97。因此,理解编码很简单,所有的编码都是字符与数字的⼀种对应关系。
ASCII编码
计算机最早出现在美国,因此⽼美搞编码只需要对26个英⽂字符⼤⼩写以及常⽤的字符对应数字就可以了,这种对应就是
ASCII(American Standard Code for Information Interchange,美国信息互换标准代码)码。标准ASCII 码使⽤7 位⼆进制数来表⽰所有的⼤写和⼩写字母,数字0 到9、标点符号, 以及在美式英语中使⽤的特殊控制字符。这样可以表⽰27=128个字符。标准ASCII码的最⾼位恒为0,没有使⽤。
⽤java输出英⽂字符的ASCII码如下:
public class TestCode {
public static void main(String[] args) throws Exception {
int code='a';
System.out.println(code);
}
}
输出:
97
iso8859-1
随着计算机的推⼴,世界各地都开始使⽤计算机。各国不同语⾔对字符编码提出了新的需求,原ASCII
的128个字符已经显得严重不⾜。那怎么办呢,ASCII码不是只⽤了⼀个字节中的7位吗,还剩余1位呢?那就赶紧⽤上吧!于是⼈们把编码扩展到了8位,即256个字符的编码,这就是ISO8859-1。这种扩展保持了与ASCII的兼容性,即最⾼位为0的ISO8859-1编码等同于ASCII码。
⽤java随便输出⼀个iso8859-1字符如下:
public class TestCode {
public static void main(String[] args) throws Exception {
char code=0xA7;
System.out.println(code);
}
}
输出:
§
GBK码
等到计算机进⼊中国,⼈们⼜头疼了。常⽤汉字就有6000多个,像ASCII那样⽤⼀个字节来编码撑爆了也不够啊。但是这难不倒智慧的中国⼈们,我们直接定下标准:⼩于127的字符与原意义相同(保持与ASCII的兼容性),但是两个⼤于127的字符连在⼀起时,就表⽰⼀个汉字。这样我们就凑出来了7000多个简体汉字的编码了。此外,这些编码还对ASCII码中已有的标点、数字、字母都⽤两字节重新编码,这就是通常说的“全⾓”字符。这种编码就是GB2312。
但是中国的汉字实在太多了,GB2312还是不够⽤,⼀些不常⽤的汉字还是显⽰不出来啊。于是我们不得不继续挖掘GB2312的潜能,⼲脆只要求第⼀个字节⼤于127⽽不管后⼀个字节的⼤⼩了。这种扩展之后的编码⽅案称为GBK。
下图为中⽂“你好”⼆字的GB2312编码输出(GBK输出相同):
public class TestCode {
public static void main(String[] args) throws Exception {
String s = "你好";
byte[] code = s.getBytes("gb2312");
for (byte b : code) {
System.out.HexString(b & 0xFF) + " ");
}
}
}
输出:
c4 e3 ba c3
Unicode码
中国造出了GBK编码,其他国家呢,他们也要显⽰⾃⼰的⽂字啊。于是各个国家都搞出了⼀套⾃⼰的编码标准,结果相互之间谁也不懂谁的编码,互不兼容。这样不⾏啊,于是乎ISO(国际标谁化组织)不得不站出来说话了:“你们都不要各⾃搞编码了,我给你们搞⼀套统⼀的!”。于是ISO搞了⼀个全球统⼀的字符集编码⽅案,叫UCS(Universal Character Set),俗称Unicode。
Unicode标准最早是1991年发布了,⽬前实际应⽤的版本是UCS-2,即使⽤两个字节编码字符。这样理论上⼀共可以编码216=65536个字符,基本能够满⾜各种语⾔的需求。
UTF8、UTF16码
其实Unicode码已经完美解决编码国际化问题了,那utf8和utf16⼜是神马东东,⽤来解决什么问题呢?
前⾯已经说过,编码只是字符与数字的⼀种对应关系,这完全是⼀个数学问题,跟计算机和存储以及⽹络都没有半⽑钱关系。Unicode码就是这样⼀种对应关系,它并没有涉及到如何存储以及传输的问题。看下⾯⼀个例⼦:
假如某个字符的Unicode编码为0xabcd,也就是两个字节。那存储的时候是哪个字节在前哪个在后呢?⽹络传输的时候⼜是先传输哪个字节呢?计算机从⽂件中读取到0xabcd⼜是怎么知道这是两个ASCII码还是⼀个Unicode码呢?
因此需要⼀种统⼀的存储和传输格式来标⽰Unicode码。这种统⼀的实现⽅式称为Unicode转换格式(Unicode Transformation Format,简称为UTF)。编码utf8和utf16就是因此⽽产⽣的。
其中utf16与16位的Unicode码完全对应。在Mac和普通PC上,对于字节顺序的理解是不⼀致的。⽐如MAC是从低字节开始读取的,因此前⽂的0xabcd如果按照所见的顺序存储,则会被MAC认为是0xcda
b,⽽windows会从⾼字节开始读取,得到的是0xabcd,这样根据Unicode码表对应出来的字符就不⼀致了。
因此,utf16使⽤了⼤端序(Big-Endian,简写为UTF-16 BE)、⼩端序(Little-Endian,简写为UTF-16 LE)以及BOM(byte order mark)的概念。如果在windows上⽤记事本写上⼀些中⽂字符并以Unicode码格式保存,然后使⽤⼗六进制查看器打开即可以看到⽂件的前两个字节为0xfffe(0xfffe在Unicode码中不对应字符),⽤来标记使⽤⼩端序存储(windows平台默认使⽤⼩端序),
下图为中⽂“你好”⼆字在windows7上的⼗六进制数据。
如果⽤java程序输出“你好”⼆字的utf16码,则如下:
public class TestCode {
public static void main(String[] args) throws Exception {
String s = "你好";
byte[] code = s.getBytes("utf16");
for (byte b : code) {
System.out.HexString(b & 0xFF) + " ");
}
}
}
输出:
fe ff 4f60597d
可以看到java默认输出的是⼤端序的utf16编码(BOM为0xfeff)。
由于Unicode统⼀采⽤16位⼆进制编码字符,试想⼀篇英⽂⽂章如果⽤UTF16来存储的话整整⽐⽤ASCII存储多占⽤⼀倍的存储空间(英⽂字符的Unicode码⾼字节是0),这样⽩⽩的浪费让⼈于⼼不忍啊。于是utf8诞⽣了。utf8是⼀种变长编码,根据不同的Unicode码值采⽤不同的存储长度。那么问
题⼜来了,既然是变长的系统怎么知道⼏个字节表⽰⼀个字符编码呢?对于这类问题计算机中通⽤的处理⽅式就是使⽤标志位,就像ip段的划分⼀样。具体如下:
0xxxxxxx,如果是这样的01串,也就是以0开头后⾯是啥就不⽤管了XX代表任意bit.就表⽰把⼀个字节做为⼀个单元.就跟ASCII完全⼀样.
110xxxxx 10xxxxxx.如果是这样的格式,则把两个字节当⼀个单元
1110xxxx 10xxxxxx 10xxxxxx 如果是这种格式则是三个字节当⼀个单元.
⽤java输出“你好”的utf8编码如下:
public class TestCode {
public static void main(String[] args) throws Exception {
String s = "你好";
byte[] code = s.getBytes("utf8");
for (byte b : code) {
System.out.HexString(b & 0xFF) + " ");
}
}
}
输出:
e4 bd a0 e5 a5 bd
我们可以跟上⾯对应⼀下,“你”字的第⼀个字节0xe4的⾼四位⼆进制是1110,因此这是⼀个三字节编码,系统识别时就⼀次读取三个字节再组合成Unicode码数字,然后就可以对应到字符“你”了。字符“好”类似。
Unicode码的发展
Unicode码采⽤16位编码世界字符其实还是有点捉襟见肘的。因此从 Unicode 3.1 版本开始,设⽴了16个辅助平⾯(相当于Unicode码⼜扩充了4位),使 Unicode 的可使⽤空间由六万多字增⾄约⼀百万
字。⽤⽩话说就是增加了⼏个区段,⽐如原始版本的Unicode码的范围是0x0000 ~ 0xffff,第⼀辅助平⾯的范围是0x10000~0x1FFFD,第⼆辅助平⾯的范围是0x20000 ~ 0x2FFFD,……
最新版的Unicode码规范提出了UCS-4,即使⽤4字节做Unicode编码。类似前⾯的utf16,对于UCS-4的Unicode码,可以采⽤utf32来存储,同样需要定义⼤⼩端序和BOM信息。
URLEncode
URL编解码是WEB开发中常⽤的编解码⽅法,这种编码不同于上⾯介绍的⼏种编码。上⽂中介绍的编码都是将⼀个字符对应到⼀个数字上,⽽URL编码则是字符替换,将⼀些⾮ASCII字符和⼀些容易引起问题的字符替换为其编码字符,解码时原样替换回来,从⽽解决url在⽹络传输中的乱码问题。
看下⾯⼀个例⼦:
输出:
我们将原url与编码后的url做⼀个对⽐(这⾥为了让原字符与编码字符对照起来加了⼀些空格):
对⽐发现,编码后http、www.baidu、username这⼀些并没有改变,“:”被替换为“%3A”, “/”被替换为“%2F”, “?”被替换为“%3F”,“=”被替换为“%3D”, “你好”被替换为“%E4%BD%A0%E5%A5%BD”,这
些%后⾯的⼗六进制字符都是哪⾥来的呢,其实就是原字符的utf8码值。前⾯我们已经看过“你好”的utf8码为“e4 bd a0 e5 a5 bd”,这些⼗六进制转换为字符串形式然后在前⾯加上% 就是URL编码了。因此解码就是将这些字符串去掉%然后⽤utf8码译出来 。
常见乱码问题
前⽂提到,⽂件保存、⽹络传输时,所保存和传输的都是字符对应的码值,因此查看⽂件时必须将这些码值反过来对应到相应的字符(解码),才能形成我们⼈能够看懂的字符串形式。如果解码时选取的解码⽅式与编码⽅式不⼀致呢,这就是乱码问题的根本原因了。看下⾯⼏个例⼦:
例⼦1
在中⽂版的windows系统(本⽂中使⽤win7 64位 简体中⽂旗舰版)桌⾯上新建txt⽂件,写上“联通”⼆字,保存,关闭。然后再双击打
开,看到了什么?哇!乱码!unicode码和ascii码区别
我们看看windows都做了什么。我们写的“联通”俩字没问题,保存,也就是将这俩字对应出⼏个数字(码值)保存起来,也没问题。等等,windows是选⽤哪种编码进⾏保存的呢?⽤搜索引擎查⼀下,原来默认选⽤ANSI编码,对应在中⽂版windows系统中就是GBK,我们
⽤⼗六进制查看器验证⼀下:果然是GBK。然后我们将这⼏个⼗六进制数字的⼆进制写出来:
public class TestUrlCode {
public static void main (String[] args) throws Exception {
String url="www.baidu?username=你好";
String encodeStr=de(url, "utf8");
System.out .println(encodeStr);
}}http%3A%2F%2Fwww.baidu%3Fusername%3D%E4%BD%A0%E5%A5%BD
http: / / www.baidu? username= 你 好http%3A%2F%2Fwww.baidu%3Fusername%3D%E4%BD%A0%E5%A5%BD
c1 1100 0001
aa 1010 1010
cd 1100 1101
a8 1010 1000
看第⼀个字节,以110开头,第⼆个字节,以10开头,第三个字节,以110开头,第四个字节,以10开头。这不正好符合utf8双字节编码格式吗?因此我们再次双击打开的时候记事本就错误地认为这是utf8
编码的⽂件,这当然的就形成乱码了。
下⾯使⽤notepad++打开,同样乱码。然后选择 格式–以ANSI格式编码 瞬间看到亲切的“联通”⼆字了!
例⼦2
做java开发的童鞋经常遇到原Project导⼊Eclipse乱码问题,如下图(其实这⼏个乱码的原字符是“你好”⼆字)
这个就⽐较简单了,因为原项⽬采⽤GBK编码保存的,新导⼊项⽬的Eclipse配置的是utf8编码,⽤utf8来解码读取gbk编码,乱码是必须滴。解决办法就是将两者保持⼀致即可。
例⼦3
有时候编码信息是在⽂件开始的地⽅声明的,⽐如xml⽂件和html⽂件,如下:
百度⾸页的源码精简后摘出⼀部分源码如下:
常⽤的xml⽂件开头标⽰如下:
这样的头部标⽰可以明确地看到⽂件的编码信息,然⽽有时候却会引发另⼀个问题(主要针对开发者):
⽐如我们写⼀个html⽂件:
然后在保存这个⽂件的时候我们不⼩⼼选择了GBK编码保存。这样问题就来了,浏览器在读取这个html⽂件的时候会按照⽂件中声明的编码utf8的规则读取,这样中⽂⼜成乱码了。(因此各位coder要注意⽂件保存时的编码,遇到类似的问题也要知道原因和解决办法)<html><head><meta http-equiv="content-type" content="text/html;charset=8”ead><body></body></html><?xml version='1.0' encoding='utf-8'?>
<html ><head ><meta charset ="utf-8"><title >test </title ></head >
<body >
aaa 你好bbb
</body >
</html >
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论