字符编码
计算机里只有数字,计算机软件里的一切都是用数字来表示的,屏幕上显示的一个个字符也不例外。最初的计算机的使用是在美国,当时所用到的字符也就是我们现在键盘上的一些符号和少数几个特殊的符号,每一个字符都用一个数字来表示,一个字节所能表示的数字范围内足以容纳所有的这些字符,实际上表示这些字符的数字的字节的最高位(bit)都为0,也就是说这些数字都在0到127之间。
字符a对应数字97,字符b对应数字98等,这种字符与数字对应的编码规则被称为ASCII(美国标准信息交换码)。ASCII的最高bit位都为0,也就是说这些数字都在0到127之间。
随着计算机逐渐在其他国家的应用和普及,许多国家都把本地的字符集引入了计算机。中国大陆将每一个中文字符都用两个字节的数字来表示,原有的ASCII字符的编码保持不变,仍用一个字节表示,为了将一个中文字符与两个ASCII码字符相区别,中文字符的每个字节的最高位bit都为1,中国大陆为每个中文字符制定的编码规则称为GB2312(国标码)。GB2312是中华人民共和国国家汉字信息交换用码,基本集共收入汉字6763个和非汉字图形字符682个,通行于中国大陆。新加破等地也使用此编码。
在GB2312的基础上,对更多的中文字符(包括繁体)进行了编码,新的编码规则称为GBK。GBK即汉字内码扩展规范,K为扩展的汉语“扩”的声母。GBK编码标准兼容GB2312,GBK是对GB2312-80的扩展,共收录汉字(包括部首和构件)21003个,符号883个,并提供1984个造字码位,简、繁体字融于一库。GBK采用双字节表示,总体编码范围为8140-FEFE,首字节在81-FE之间,尾字节在40-FE之间,剔除xx7F一条线,总计23940个码位。有些汉字用五笔和拼音都打不出来,如:溱(五笔IDWI),须调出GBK字符集才能打出这个字。
在中国大陆使用的计算机系统上,GBK和GB2312就被称为该系统的本地字符集。
“中国”的“中”字,在中国大陆的编码是十六进制的D6D0,而在中国台湾的编码是十六进制的A4A4,台湾地区对中文字符集的编码规则称为BIG5(大五码)。
在一个国家的本地化系统中出现的一个字符,通过电子邮件传送到另外一个国家的本地化系统中,看到的就不是那个原始字符了,而是另外那个国家的一个字符或乱码。
Unicode编码
- ISO(国际标准化组织)将全世界所有的符号进行了统一编码,称之为Unicode编码。
- “中”这个符号,在全世界的任何角落始终对应的都是一个十六进制的数字4e2d。
- 如果所有的计算机系统都使用Unicode编码,在中国大陆的本地化系统中显示的“中”这个符号,发送到伊拉克的本地化系统中,显示的仍然是“中”这个符号。
- Unicode编码的字符都占用两个字节的大小(65535),对于ASCII码所表示的字符只是简单地在ASCII码原来占用的一个字节前面,增加一个所有bits为0的字节。
- Unicode只占用两个字节,在全世界范围内所表示的字符个数不会超过2的16次方(65536),实际上,Unicode编码中还保留了两千多个数值没有用于字符编码。
- 在相当长的一段时期内,本地化字符编码将与Unicode编码共存。
- Java中的字符使用的都是Unicode编码,Java在通过Unicode保证跨平台特性的前提下,也支持本地平台字符集。而我们显示输出和键盘输入都是采用本地编码。
UTF-8编码
ASCII码字符保持原样,仍然只占用一个字节,对于其他国家的字符,UTF-8使用两个或三个字节来表示。使用UTF-8编码的文件,通过都要用EF BB BF作为文件开头的三个字节数据。
UTF-8的优点:
- 不出现内容为0x00字节
- 使用应用程序检测数据在传输过程中是否发生了错误
- 直接处理使用ASCII码的英文文档
- UTF-8的缺点:
- 某些字符需要使用3个字节
UTF-16
UFT-16编码在Unicode基础上进行了一些细节上的扩充,增加了对Unicode编码没有包括的那些字符的表示方式。
UTF-16对Unicode的扩充并没有影响Unicode编码所包括的那些字符,只是增加了对Unicode编码没有包括的那些字符的表示方式,一个使用Unicode编码的字符就是UTF-16格式的。
Unicode编码将0xD800-0xDFFF区间的数值保留出来(共2048个),UTF-16扩充的字符,占用4个字节,前面两个字节的数值为0xD800-0xD8FF之间,后面两个字节的数值为0xDC00-0xDFFF之间。
为什么不让前面和后面的两个字节的数值都位于0xD800-0xDFFF之间呢?
在不同体系结构的计算机系统中,UTF-16编码的Unicode字符在内存中的字节存储顺序是不同的。
对于0x1234这样一个双字节数据,使用Little-Endian和Big-Endian两种方式在内存中存储的格式,如图:
如果文件用 0xFE 0xFF这两个字节开头,则表明文本的其余部分是Big-Endian的UTF-16编码;如果文件以0xFF 0xFE这两个字节开头,则表明文本的其余部分是Little-Endian的UTF-16编码。
字符编码的编程体验
打印中文字符的Unicode码
打印中文字符的GB2312码
验证写入到屏幕输出流的中文字符所采用的编码
查看系统的缺省编码
修改系统的缺省编码
验证从键盘输入流中读取的中文字符所采用的编码
研究GB2312码到Unicode码的解码过程
GB2312码的中文字符被按照ISO8859-1字符集解码生成了Unicode字符串后,如何将这个字符串转换成正确的Unicode编码字符串
public class CharCode
{
public static void main(String[] args) throws Exception
{
//iso8859-1:西方国家使用的字符集
System.setProperty("file.encoding","ISO8859_1");//修改JAVA系统使用的默认字符编码为iso8859-1
System.getProperties().list(System.out);//打印JAVA系统属性,从中查看file.encoding字符集
String strChina="中国";
for(int i=0;i<strChina.length();i++)
{
//charAt(i)方法获得字符串的第i个字符
//一般查看字符的编码是用16进制的,要查看一个整数的16进制形式,可以用
//toHexString()方法,把整数转换为16进制显示
System.out.println(Integer.toHexString((int)strChina.charAt(i)));//打印中国的Unicode编码,并用16进制显示
}
byte [] buf=strChina.getBytes("gb2312");//将字符串转换成gb2312编码后保存到数组
for(int j=0;j<buf.length;j++)
{
System.out.println(Integer.toHexString(buf[j]));//打印中国的gb2312编码,并用16进制显示
}
for(int k=0;k<buf.length;k++)
{
System.out.write(buf[k]);//将中国的gb2312编码写到显示器
}
System.out.println();
}
}
public class CharDecode
{
public static void main(String[] args) throws Exception
{
System.getProperties().put("file.encoding","ISO8859_1");
System.out.println("Please enter a Chinese String!");
byte [] buf=new byte[1024];
int pos=0;
String strInfo=null;
int ch=0;
while(true)
{
ch=System.in.read();
System.out.println(Integer.toHexString(ch));
switch(ch)
{
case '\r':
break;
case '\n':
strInfo=new String(buf,0,pos);
for(int i=0;i<strInfo.length();i++)
{
System.out.println(Integer.toHexString((int)strInfo.charAt(i)));
}
System.out.println(new String(strInfo.getBytes("ISO8859_1"),"gb2312"));
break;
default:
buf[pos++]=(byte)ch;
}
}
}
}
public class Test
{
public static void main(String[] args) throws Exception {
byte[] buf = null;
String str = null;
buf = "中国".getBytes(); //把"中国"按照默认字符集(GBK)来解成字节数组
str = new String(buf,"iso8859-1"); //把buf字节数组按照默认字符集(gbk)来生成字符串
System.out.println(str); //打印生成的字符串,显示不出正确的中文字符
buf = str.getBytes("iso8859-1"); //把str字符串按照"iso8859-1"的字符集来解成字节数组
str = new String(buf,"gb2312"); //把buf字节数组按照"iso8859-1"字符集来生成字符串
System.out.println(str); //打印生成的字符串,显示正确的中文字符
}
}