一、前言
最近项目中用到招商银行的企业直连功能,通过接口的方式直接调用招商银行的前置机(http://www.cmbchina.com/corporate/firmbank/FirmbankInfo.aspx?guid=d0917853-6256-44ea-b1e2-24b8524042db)而后进行转账和信息的查询。但招商银行的数据是通过gbk格式进行传输的,而我们的系统使用的utf8编码格式,理所当然的就出现了乱码问题。
借此机会,对编码及Java中如何解决乱码进行汇总。
如果只是想要知道如何解决该问题,可以直接阅读4.2。
如果想对此有全面的了解,请接着向下看。
二、基础知识
1.为什么要编码
大家都知道,计算机存储数据是以0、1进行数据的存储,而人类的语言则多种多样,要想让计算机能够理解众多的人类语言,就必须将人类语言“翻译”成计算机能够看懂的语言,这就是为什么要进行编码。编码的目的就是为了让计算机能够理解人类的语言。
2.编码格式是什么?
通俗的讲编码格式就是“翻译”的规则,人类语言与计算机语言的对应规则。由于人类语言众多,编码格式的种类相应的也有多种。
3.为什么会出现乱码问题?
常见的乱码问题就是因为没有使用正确的编码格式进行信息的转换,导致获得的信息看不懂。如同“计算机”这个词,这是中文的表达方式,转换成英文是“computer”,但如果你用日语的转换规则进行翻译就是“コンピュータ”,一个不懂日语的人肯定不知道这表示什么意思,就认为是乱码了。
4.乱码问题解决原则
也就是说,解决乱码问题的一个必要条件是需要对信息的原始编码格式和信息的目标的编码格式都了解。是不是知道信息的原始编码格式和信息的目标的编码格式就一定能解决乱码问题?答案是否定的。有些词在这个语言中有,而在另一种语言中的情况是存在的,对应到计算机的世界中也是如此,为了能够确保“翻译”正确,有时需要引入第三种编码格式作为桥梁。
三、常用编码规格
1.ASCII 码
学过计算机的人都知道 ASCII 码,ASCII 码是美国标准信息交换代码(American Standard Code for Information Interchange)的缩写, 为美国英语通信所设计。它由128个字符组成,包括大小写字母、数字0-9、标点符号、非打印字符(换行符、制表符等4个)以及控制字符(退格、响铃等)组成。 总共有 128 个,用一个字节的低 7 位表示,0~31 是控制字符如换行回车删除等;32~126 是打印字符,可以通过键盘输入并且能够显示出来。 例如'A'是65,'a'是97。
但是,由于它是针对英语设计的,当处理带有音调标号(形如汉语的拼音)的欧洲文字时就会出现问题。
2.ISO-8859-1
128 个字符显然是不够用的,于是 ISO 组织在 ASCII 码基础上又制定了一些列标准用来扩展 ASCII 编码,它们是 ISO-8859-1~ISO-8859-15,其中 ISO-8859-1 涵盖了大多数西欧语言字符,所有应用的最广泛。ISO-8859-1 仍然是单字节编码,它总共能表示 256 个字符。
3.GB2312
它的全称是《信息交换用汉字编码字符集基本集》,它是双字节编码,总的编码范围是 A1-F7,其中从 A1-A9 是符号区,总共包含 682 个符号,从 B0-F7 是汉字区,包含 6763 个汉字:其中一级汉字3755个,二级汉字3008个;同时,GB2312收录了包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的682个全角字符。
GB2312基本满足了汉字的计算机处理需要,它所收录的汉字已经覆盖中国大陆99.75%的使用频率。4.GBK
GBK是汉字编码标准之一,全称《汉字内码扩展规范》(GBK即“国标”、“扩展”汉语拼音的第一个字母,英文名称:Chinese Internal Code Specification)。GBK编码,是在GB2312-80标准基础上的内码扩展规范,使用了双字节编码方案,其编码范围从8140至FEFE(剔除xx7F),共23940个码位,共收录了21003个汉字,完全兼容GB2312-80标准,支持国际标准ISO/IEC10646-1和国家标准GB13000-1中的全部中日韩汉字,并包含了BIG5编码中的所有汉字。GBK 向下与 GB 2312 编码兼容,向上支持 ISO 10646.1 国际标准,是前者向后者过渡过程中的一个承上启下的标准。
5.Unicode
Unicode(Universal Code 统一码)是基于通用字符集(Universal Character Set)的标准来发展, Unicode是国际组织制定的可以容纳世界上所有文字和符号的字符编码方案。它通过增加一个高字节对ISO Latin-1字符集进行扩展,当这些高字节位为0时,低字节就是ISO Latin-1字符。UNICODE支持欧洲、非洲、中东、亚洲(包括统一标准的东亚象形汉字和韩国象形文字)。但是,UNICODE并没有提供对诸如Braille, Cherokee, Ethiopic, Khmer, Mongolian, Hmong, Tai Lu, Tai Mau文字的支持。同时它也不支持如Ahom, Akkadian, Aramaic, Babylonian Cuneiform, Balti, Brahmi, Etruscan, Hittite, Javanese, Numidian, Old Persian Cuneiform, Syrian之类的古老文字。
事实证明,对可以用ASCII表示的字符使用UNICODE并不高效,因为UNICODE比ASCII占用大一倍的空间,而对ASCII来说高字节的0对他毫无用处。为了解决这个问题,就出现了一些中间格式的字符集,他们被称为通用转换格式,即UTF(Universal Transformation Format)。
unicode编码规范中常用的是utf-16和utf-8。
6.UTF-16
UTF-16 用两个字节来表示 Unicode 转化格式,这个是定长的表示方法,不论什么字符都可以用两个字节表示,两个字节是 16 个 bit,所以叫 UTF-16。UTF-16 表示字符非常方便,每两个字节表示一个字符,这个在字符串操作时就大大简化了操作,这也是 Java 以 UTF-16 作为内存的字符存储格式的一个很重要的原因。
UTF-16 统一采用两个字节表示一个字符,虽然在表示上非常简单方便,但是也有其缺点,有很大一部分字符用一个字节就可以表示的现在要两个字节表示,存储空间放大了一倍,在现在的网络带宽还非常有限的今天,这样会增大网络传输的流量,而且也没必要。
7.UTF-8
UTF-8 采用了一种变长技术,每个编码区域有不同的字码长度。UTF-8用1到6个字节编码UNICODE字符。如果UNICODE字符由2个字节表示,则编码成UTF-8很可能需要3个字节。而如果UNICODE字符由4个字节表示,则编码成UTF-8可能需要6个字节。用4个或6个字节去编码一个UNICODE字符可能太多了,但很少会遇到那样的UNICODE字符。UTF-8 可以在同一个页面显示中文简体繁体及其它语言(如日文,韩文) 。
实际表示ASCII字符的UNICODE字符,将会编码成1个字节,并且UTF-8表示与ASCII字符表示是一样的。所有其他的UNICODE字符转化成UTF-8将需要至少2个字节。
小结:
对中文字符后面四种编码格式都能处理,GB2312 与 GBK 编码规则类似,但是 GBK 范围更大,它能处理所有汉字字符,所以 GB2312 与 GBK 比较应该选择 GBK。UTF-16 与 UTF-8 都是处理 Unicode 编码,它们的编码规则不太相同,相对来说 UTF-16 编码效率最高,字符到字节相互转换更简单,进行字符串操作也更好。它适合在本地磁盘和内存之间使用,可以进行字符和字节之间快速切换,如 Java 的内存编码就是采用 UTF-16 编码。但是它不适合在网络之间传输,因为网络传输容易损坏字节流,一旦字节流损坏将很难恢复,想比较而言 UTF-8 更适合网络传输,对 ASCII 字符采用单字节存储,另外单个字符损坏也不会影响后面其它字符,在编码效率上介于 GBK 和 UTF-16 之间,所以 UTF-8 在编码效率上和编码安全性上做了平衡,是理想的中文编码方式。
三、Java编码相关知识
1. byte(字节)和char(字符)
两个基本概念
字符:人类使用的记号,抽象意义上的一个符号。比如‘中’、‘x’。
字节:计算机中存储数据的单元,一个8位的二进制数,是一个很具体的存储空间。
在Java中byte和char都是基本数据类型:byte(字节)占8位(8 bit),它的值域被定义为-128~127。char(字符)为两个字节(2 bytes)。
在进行数据存储和传输时必然会涉及到字节和字符间的转换。字符和字节的对应关系因编码格式的不同而有所变化,具体见下面的示例。
需要和Java中的byte和char基本数据类型区分开,byte(字节)占8位(8 bit),它的值域被定义为-128~127。char(字符)为两个字节(2 bytes)。char和byte的对应关系是固定的。
Java语言中基本类型所占存储空间的大小是固定的,它们的大小并不像其它大多数语言那么随机器硬件架构的变化而变化。这种所占存存储空间大小的不变性是java程序具有可移值性的原因之一。
2.编码示例
前面讲的都是基础知识,现在用一个示例直观的了解下同一个字符串在不同的编码格式下的编码结果。
public static void getBytesTest() {String name = "xjp zx";//(前三个字符是最新的主席的名字,但博客园里无法发表,请自行替换成汉字)
System.out.print("原始字符: ");
for (char b : name.toCharArray()) {System.out.print(b + " ");
}System.out.println();try {
byte[] iso8859 = name.getBytes("ISO-8859-1");printByte("ISO-8859-1",iso8859);
byte[] gb2312 = name.getBytes("GB2312");printByte("GB2312",gb2312);
byte[] gbk = name.getBytes("GBK");printByte("GBK",gbk);
byte[] utf16 = name.getBytes("UTF-16");printByte("UTF-16",utf16);
byte[] utf8 = name.getBytes("UTF-8");printByte("UTF-8",utf8);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();}}public static void printByte(String charsetName,byte[] bytes) {System.out.print("编码格式为 " +charsetName +" 时的bytes值: ");for (byte b : bytes) {System.out.print(b + " ");
}System.out.println();}代码的默认编码是gbk格式的.程序执行的结果如下:从执行结果可以看出:1)英文字母在各种编码格式中均是一个byte,且对应的byte值均相同.2)ISO-8859-1编码格式下一个字符为一个byte3)GB2312和GBK的编码结果基本上相同4)UTF-16编码格式每一个字符均是两个byte5)UTF-8编码格式下一个中文字符是3个byte,英文字符是1个byte
四、Java中编码转换
1.基本原理
通过上面的示例可以知道,同一个中文字符在不同的编码格式下会有不同的byte值,如果想进行正确的转码,就需要得到对应的正确的byte值,这个可以通过String类的getBytes(String charsetName)方法获得。在获得正的byte值后利用String类的String(byte bytes[], String charsetName)构造函数重新生成一个新的String对象即可。
2.转换示例代码
下面的示例描述了将一个gbk编码的字符串转为utf-8格式,再将utf-8格式的字符串转为gbk格式。
public static void doubleTranslate() throws UnsupportedEncodingException {String gbk = "业务参考号重复";
System.out.print("GBK格式下的bytes: ");
for (byte b : gbk.getBytes("GBK")) {System.out.print(b+" ");
}System.out.println();System.out.print("UTF-8格式下的bytes: ");
String utf8 = new String(gbk.getBytes("UTF-8"),"UTF-8"); //转换为UTF-8for (byte b : utf8.getBytes("UTF-8")) {System.out.print(b+" ");
}System.out.println();System.out.println("gbk转为utf-8: "+utf8);
System.out.println("utf-8转为gbk: "+new String(utf8.getBytes("GBK"),"GBK"));}代码执行结果为:
五、总结及进一步的工作
1.小结:
本文首先对编码知识进行了简要介绍,而后通过示例展示了同一个字符串在不同编码格式下的byte值,而后通过示例演示了在Java中如何进行编码格式的转换。
转换成功的前提是获得要转换编码格式的byte值,而后利用String类的构造函数即可。
2.进一步的工作
本文只是说明了怎么做,但是对其背后的实现原理没有进行介绍。知其然,知其所以然,才是好的学习习惯。接下来会另起一篇介绍转码后面的实现机理。
参考资料:
http://www.blogjava.net/liuyz2006/articles/385768.html
http://blog.csdn.net/baijinwen/article/details/1623544
百度百科及wiki百科