Java中的中文编码问题

来源:互联网 发布:ehr人力资源软件 编辑:程序博客网 时间:2024/05/21 07:49
(1)、为什么要编码
由于人类的语言有太多,因而表示这些语言的符号太多,无法用计算机中一个基本的存储单元byte 来表示,因而必须要经过拆分或一些翻译工作,才能让计算机能理解。
所以总的来说,编码的原因可以总结为:
1.计算机中存储信息的最小单元是一个字节即 8 个 bit,所以能表示的字符范围是 0~255 个
2.人类要表示的符号太多,无法用一个字节来完全表示
3.要解决这个矛盾必须需要一个新的数据结构 char,从 char 到 byte 必须编码

(2)、编码方式
ASCII 码
ASCII 码,总共有 128 个,用一个字节的低 7 位表示,0~31 是控制字符如换行回车删除等;32~126 是打印字符,可以通过键盘输入并且能够显示出来。

ISO-8859-1
128 个字符显然是不够用的,于是 ISO 组织在 ASCII 码基础上又制定了一些列标准用来扩展 ASCII 编码,它们是 ISO-8859-1~ISO-8859-15,其中 ISO-8859-1 涵盖了大多数西欧语言字符,所有应用的最广泛。ISO-8859-1 仍然是单字节编码,它总共能表示 256 个字符。

GB2312
它的全称是《信息交换用汉字编码字符集 基本集》,它是双字节编码,总的编码范围是 A1-F7,其中从 A1-A9 是符号区,总共包含 682 个符号,从 B0-F7 是汉字区,包含 6763 个汉字。

GBK
全称叫《汉字内码扩展规范》,是国家技术监督局为 windows95 所制定的新的汉字内码规范,它的出现是为了扩展 GB2312,加入更多的汉字,它的编码范围是 8140~FEFE(去掉 XX7F)总共有 23940 个码位,它能表示 21003 个汉字,它的编码是和 GB2312 兼容的,也就是说用 GB2312 编码的汉字可以用 GBK 来解码,并且不会有乱码。

UTF-16
说到 UTF 必须要提到 Unicode(Universal Code 统一码),ISO 试图想创建一个全新的超语言字典,世界上所有的语言都可以通过这本字典来相互翻译。
UTF-16 具体定义了 Unicode 字符在计算机中存取方法。UTF-16 用两个字节来表示 Unicode 转化格式,这个是定长的表示方法,不论什么字符都可以用两个字节表示,两个字节是 16 个 bit,
所以叫 UTF-16。UTF-16 表示字符非常方便,每两个字节表示一个字符,这个在字符串操作时就大大简化了操作,这也是 Java 以 UTF-16 作为内存的字符存储格式的一个很重要的原因。

UTF-8
UTF-16 统一采用两个字节表示一个字符,虽然在表示上非常简单方便,但是也有其缺点,有很大一部分字符用一个字节就可以表示的现在要两个字节表示,存储空间放大了一倍,
在现在的网络带宽还非常有限的今天,这样会增大网络传输的流量,而且也没必要。而 UTF-8 采用了一种变长技术,每个编码区域有不同的字码长度。不同类型的字符可以是由 1~6 个字节组成。
UTF-8 有以下编码规则:
1.如果一个字节,最高位(第 8 位)为 0,表示这是一个 ASCII 字符(00 - 7F)。可见,所有 ASCII 编码已经是 UTF-8 了。
2.如果一个字节,以 11 开头,连续的 1 的个数暗示这个字符的字节数,例如:110xxxxx 代表它是双字节 UTF-8 字符的首字节。

3.如果一个字节,以 10 开始,表示它不是首字节,需要向前查找才能得到当前字符的首字节

[java] view plaincopyprint?
  1.   
  2. /** 
  3.  * byte Array toHex 
  4.  * @param b 
  5.  * @return 
  6.  */  
  7. public static String toHex(byte[] b) {  
  8.     String result = "";  
  9.     for (int i = 0; i < b.length; i++) {  
  10.         String hex = Integer.toHexString(b[i] & 0xFF);  
  11.         if (hex.length() == 1) {//高四位置为0  
  12.             hex = '0' + hex;  
  13.         }  
  14.         result = result + " " +hex.toUpperCase();  
  15.     }  
  16.     return result;  
  17. }  
  18.   
  19. /** 
  20.  * char to byte array 
  21.  * @param c 
  22.  * @return 
  23.  */  
  24. public static byte[] charToByte(char c) {  
  25.        byte[] b = new byte[2];  
  26.        b[0] = (byte) ((c & 0xFF00) >> 8);  
  27.        b[1] = (byte) (c & 0xFF);  
  28.        return b;  
  29.    }  
  30.   
  31. /** 
  32.  * char array toHex 
  33.  * @param c 
  34.  */  
  35. public static void charToHex(char[] c){  
  36.     for(char cc:c){  
  37.         byte[] b =charToByte(cc);  
  38.         System.out.print(toHex(b));  
  39.     }  
  40. }  
  41.   
  42. public static void main(String[] args) {  
  43.     String name = "I am 君山";  
  44.     charToHex(name.toCharArray());  
  45.     System.out.println();  
  46.     try {  
  47.         byte[] iso8859 = name.getBytes("ISO-8859-1");  
  48.         System.out.println(toHex(iso8859));  
  49.         byte[] gb2312 = name.getBytes("GB2312");  
  50.         System.out.println(toHex(gb2312));  
  51.         byte[] gbk = name.getBytes("GBK");  
  52.         System.out.println(toHex(gbk));  
  53.         byte[] utf16 = name.getBytes("UTF-16");  
  54.         System.out.println(toHex(utf16));  
  55.         byte[] utf8 = name.getBytes("UTF-8");  
  56.         System.out.println(toHex(utf8));  
  57.     } catch (UnsupportedEncodingException e) {  
  58.         e.printStackTrace();  
  59.     }  
  60. }  
输出结果:

 00 49 00 20 00 61 00 6D 00 20 54 1B 5C 71
 49 20 61 6D 20 3F 3F
 49 20 61 6D 20 BE FD C9 BD
 49 20 61 6D 20 BE FD C9 BD
 FE FF 00 49 00 20 00 61 00 6D 00 20 54 1B 5C 71
 49 20 61 6D 20 E5 90 9B E5 B1 B1


字符串“I am 君山”用 ISO-8859-1 编码,下面是编码结果:


Figure xxx. Requires a heading

7 个 char 字符经过 ISO-8859-1 编码转变成 7 个 byte 数组.中文“君山”被转化成值是 3f 的 byte。3f 也就是“?”字符,所以经常会出现中文变成“?”很可能就是错误的使用了 ISO-8859-1 这个编码导致的。


字符串“I am 君山”用 GB2312 编码,下面是编码结果:


Figure xxx. Requires a heading

GB2312编码:GB2312 字符集有一个 char 到 byte 的码表,不同的字符编码就是查这个码表找到与每个字符的对应的字节,然后拼装成 byte 数组。如果查到的码位值大于 oxff 则是双字节,否则是单字节。双字节高 8 位作为第一个字节,低 8 位作为第二个字节。

从结果可以看出前 5 个字符经过编码后仍然是 5 个字节,而汉字被编码成双字节,在第一节中介绍到 GB2312 只支持 6763 个汉字,所以并不是所有汉字都能够用 GB2312 编码。


字符串“I am 君山”用 GBK 编码,下面是编码结果:


Figure xxx. Requires a heading

你可能已经发现上图与 GB2312 编码的结果是一样的,没错 GBK 与 GB2312 编码结果是一样的,由此可以得出 GBK 编码是兼容 GB2312 编码的,它们的编码算法也是一样的。不同的是它们的码表长度不一样,GBK 包含的汉字字符更多。所以只要是经过 GB2312 编码的汉字都可以用 GBK 进行解码,反过来则不然。


字符串“I am 君山”用 UTF-16 编码,下面是编码结果:


Figure xxx. Requires a heading

用 UTF-16 编码将 char 数组放大了一倍,单字节范围内的字符,在高位补 0 变成两个字节,中文字符也变成两个字节。从 UTF-16 编码规则来看,仅仅将字符的高位和地位进行拆分变成两个字节。特点是编码效率非常高,规则很简单。


字符串“I am 君山”用 UTF-8 编码,下面是编码结果:


Figure xxx. Requires a heading

UTF-16 虽然编码效率很高,但是对单字节范围内字符也放大了一倍,这无形也浪费了存储空间,另外 UTF-16 采用顺序编码,不能对单个字符的编码值进行校验,如果中间的一个字符码值损坏,后面的所有码值都将受影响。而 UTF-8 这些问题都不存在,UTF-8 对单字节范围内字符仍然用一个字节表示,对汉字采用三个字节表示。


常见问题分析:

中文变成了看不懂的字符,例如,字符串“淘!我喜欢!”变成了“ì ? £ ?? ò ?2?? £ ?”
字符串在解码时所用的字符集与编码字符集不一致导致汉字变成了看不懂的乱码,而且是一个汉字字符变成两个乱码字符。

一个汉字变成一个问号 例如,字符串“淘!我喜欢!”变成了“??????”
将中文和中文符号经过不支持中文的 ISO-8859-1 编码后,所有字符变成了“?”,这是因为用 ISO-8859-1 进行编解码时遇到不在码值范围内的字符时统一用 3f 表示,这也就是通常所说的“黑洞”,所有 ISO-8859-1 不认识的字符都变成了“?”。

原创粉丝点击