Java中文乱码相关问题深入剖析

来源:互联网 发布:末世之变种崛起下载知 编辑:程序博客网 时间:2024/05/15 17:12

使用Java语言时,遇到中文乱码是最令人头疼的问题之一。很多从VCC#转过来的程序员尤其不适应。鉴于目前网上大多数文章治标不治本,有必要写一篇文章来专门释疑。

1.   Byte开始

首先看一段代码:

 

[java] view plaincopy
  1. public static void startWithByte() {  
  2.   
  3.         byte[] unicode_b = new byte[4];  
  4.   
  5.         unicode_b[0] = (byte0XFE;  
  6.   
  7.         unicode_b[1] = (byte0XFF;  
  8.   
  9.         unicode_b[2] = (byte0X4E;  
  10.   
  11.         unicode_b[3] = (byte0X2D;  
  12.   
  13.         String unicode_str;  
  14.   
  15.   
  16.   
  17.         byte[] gbk_b = new byte[2];  
  18.   
  19.         gbk_b[0]=(byte0XD6;  
  20.   
  21.         gbk_b[1]=(byte0XD0;  
  22.   
  23.         String gbk_str;  
  24.   
  25.           
  26.   
  27.         byte[] utf_b=new byte[3];  
  28.   
  29.         utf_b[0]=(byte)0XE4;  
  30.   
  31.         utf_b[1]=(byte)0XB8;  
  32.   
  33.         utf_b[2]=(byte)0XAD;  
  34.   
  35.         String utf_str;  
  36.   
  37.           
  38.   
  39.         try {  
  40.   
  41.                unicode_str = new String(unicode_b, "unicode");  
  42.   
  43.                System.out.println("unicode string is:"+unicode_str);  
  44.   
  45.                  
  46.   
  47.                gbk_str = new String(gbk_b,"gbk");  
  48.   
  49.                System.out.println("gbk string is:"+gbk_str);  
  50.   
  51.                  
  52.   
  53.                utf_str=new String(utf_b,"utf-8");  
  54.   
  55.                System.out.println("utf-8 string is:"+utf_str);  
  56.   
  57.                  
  58.   
  59.         } catch (UnsupportedEncodingException e) {  
  60.   
  61.                e.printStackTrace();  
  62.   
  63.         }  
  64.   
  65.  }  

运行这段代码会得到如下输出:

unicode string is:中

gbk string is:中

utf-8 string is:中

这说明,那三组byte数组,都存储了同一个汉字“中”。在Unicode中,使用了四个字节FE FF 4E 2D(事实上,前两个字节是用来指示字节高低位的,在Unicode中,总是使用两个字节来表示一个字符,不管是中文还是ASCII字符);在GBK中,使用了两个字节D6 D0;在UTF-8中,使用了三个字节E4 B8 AD

需要说明的是,在文件中也是如此存储。你可以使用例如UltraEdit之类的工具,使用十六进制模式输入以上的几组字节,保存为文本文件。然后使用Java读入字节数组,再转化为String,可得到同样的结果。

2.   字节和字符

Java只处理两种信息,字节和字符。其中字节是无意义的,就是01的集合;而字符是有意义的,一个字符可以代表一个ASCII字符、一个汉字或者一个日本字。某些字节是可以转化为字符的,其翻译形式就是编码,UNICODE是一种编码,GBKUTF-8也各是一种编码。所有的字符和字符串都可以翻译为字节,其翻译形式是解码,与编码对应,可以对字符串进行UNICODEGBK或者UTF-8解码。

需要注意的是,Java程序内部总是以UNICODE的方式来理解字符。因此每个Java字符都有两个字节的长度,即16位。看下面的程序:

[java] view plaincopy
  1. public static void byteAndChar() {  
  2.   
  3.         char c1 = 0X4E2D;  
  4.   
  5.         System.out.println(c1);  
  6.   
  7.         char c2 = 20013;  
  8.   
  9.         System.out.println(c2);  
  10.   
  11.         byte[] b = new byte[2];  
  12.   
  13.         b[0] = 0X4E;  
  14.   
  15.         b[1] = 0x2D;  
  16.   
  17.         String str = null;  
  18.   
  19.         try {  
  20.   
  21.                str = new String(b, "unicode");  
  22.   
  23.         } catch (UnsupportedEncodingException e) {  
  24.   
  25.                e.printStackTrace();  
  26.   
  27.         }  
  28.   
  29.         char c3 = str.charAt(0);  
  30.   
  31.         System.out.println(c3);  
  32.   
  33.           
  34.   
  35.         String str2="中a";  
  36.   
  37.         System.out.println("length of str2 is :"+str2.length());  
  38.   
  39.  }  


运行程序可得:

length of str2 is :2

因此我们可以知道,汉字“中”在java程序中的值就是20013(或者十六进制的0X4E2D),Java程序在对待“中”和ASCII的字符a时并无不同,都是看作一个Unicode字符。在计算字符串长度时也是如此。

3.   Java的基本变量

了解以上基础后,再来复习一下Java的基本变量(以下表格摘自《Java编程思想》第四版)。Java语言的特点之一就是基本变量的长度在不同的平台下保持不变:

基本类型

大小

最小值

最大值

包装器类型

boolean

-

-

-

Boolean

char

16-bit

Unicode0

Unicode216-1

Character

byte

8-bit

-128

127

Byte

short

16-bit

-215

215-1

Short

int

32-bit

-231

231-1

Integer

long

64-bit

-263

263-1

Long

float

32-bit

IEEE754

IEEE754

Float

double

64-bit

IEEE754

IEEE754

Double

void

-

-

-

Void

从以上表格中我们发现,byte永远是8位,一个字节;char16位,两个字节,正好放下一个Unicode字符;short也是16位,因此在很多时候可以将shortchar进行无损的转换。

4.   字符串String

首先陈述几个事实:

1.String是由一个char的原生数组封装而成,String中存储的是一个个的char变量;

2.由于每个char中存储了一个双字节的Unicode字符,因此String中所存储的都是Unicode字符;

3.Stringbyte[]可以相互转换,编码使用new String(byte[],charset);解码使用String.getBytes()

4.若在String中出现乱码,只有一个原因,就是String中的Unicode字符没有被正确理解,或者其中的字符不是Unicode字符;

5.推论是,用何种编码格式进行编码,就应该使用相同的格式进行解码。

看下面的例子:

[java] view plaincopy
  1. public static void stringExample() {  
  2.   
  3.          String str = "中国ABC";  
  4.   
  5.          try {  
  6.   
  7.                 byte[] unicode_b = str.getBytes("unicode");  
  8.   
  9.                 for (byte b : unicode_b) {  
  10.   
  11.                        System.out.format("%02X ", b);  
  12.   
  13.                 }  
  14.   
  15.                 System.out.println();  
  16.   
  17.                 String unicode_str = new String(unicode_b, "unicode");  
  18.   
  19.                 System.out.println(unicode_str);  
  20.   
  21.   
  22.   
  23.                 byte[] utf_b = str.getBytes("utf-8");  
  24.   
  25.                 for (byte b : utf_b) {  
  26.   
  27.                        System.out.format("%02X ", b);  
  28.   
  29.                 }  
  30.   
  31.                 System.out.println();  
  32.   
  33.                 String utf_str = new String(utf_b, "utf-8");  
  34.   
  35.                 System.out.println(utf_str);  
  36.   
  37.   
  38.   
  39.                 byte[] gbk_b = str.getBytes("gbk");  
  40.   
  41.                 for (byte b : gbk_b) {  
  42.   
  43.                        System.out.format("%02X ", b);  
  44.   
  45.                 }  
  46.   
  47.                 System.out.println();  
  48.   
  49.                 String gbk_str = new String(gbk_b, "gbk");  
  50.   
  51.                 System.out.println(gbk_str);  
  52.   
  53.          } catch (UnsupportedEncodingException e) {  
  54.   
  55.                 e.printStackTrace();  
  56.   
  57.          }  
  58.   
  59.   }  


运行结果:

FE FF 4E 2D 56 FD 00 41 00 42 00 43

中国ABC

E4 B8 AD E5 9B BD 41 42 43

中国ABC

D6 D0 B9 FA 41 42 43

中国ABC

由结果可知,Unicode中每个字符由两个字节存储,

=4E 2D

=56 FD

A=41 00

B=42 00

C=43 00

UTF-8中,汉字由多个字节(2,3,4个)存储,但ASCII字符由一个字节存储:

=E4 B8 AD

=E5 9B BD

A=41

B=42

C=43

GBK中,汉字由两个字节存储,ASCII字符由一个字节存储:

=D6 D0

=B9 FA

A=41

B=42

C=43

5.   Java如何使用编码

好了,现在我们知道从字节到字符进行转换时,需要使用编码,那么Java会默认使用什么编码呢?

看下面的程序:

[java] view plaincopy
  1. public static void getEncoding(){  
  2.   
  3.          String encodeName = System.getProperty("file.encoding");  
  4.   
  5.          System.out.println("System file encoding is " + encodeName);  
  6.   
  7.  }  


输出为:

System file encoding is UTF-8

这说明我当前的这个java程序默认使用的是UTF-8编码。值得一提的是,中文Windows操作系统多使用GBK编码。但是,在Eclipse等开发工具中可以设定默认编码,比如我的Eclipse的默认编码设定为UTF-8。如此一来,此程序在Eclipse中运行时使用UTF-8编码,但是编译为class文件在Windows中运行时则使用GBK编码。

因此,为了避免混乱,最好在任何编码解码过程中都明确指定编码格式。

6.   如何产生乱码

要产生乱码很简单,将一些字节以错误的编码读入,则可以产生乱码。如下:

[java] view plaincopy
  1. public static void getEncodingError() {  
  2.   
  3.         String str = "产生乱码";  
  4.   
  5.         byte[] bs;  
  6.   
  7.         try {  
  8.   
  9.                bs = str.getBytes("unicode");  
  10.   
  11.                String strError = new String(bs, "utf-8");  
  12.   
  13.                System.out.println(strError);  
  14.   
  15.         } catch (UnsupportedEncodingException e) {  
  16.   
  17.                e.printStackTrace();  
  18.   
  19.         }  
  20.   
  21.  }  


输出如下:

��Nu_Nqx_

7.   源文件的乱码问题

对于用来编译的java源文件来讲,同样存在乱码问题。例如A.java是用unicode格式来存储的,那么在一个默认编码为GBK的系统上使用javac命令进行编译就会报错。例如一个源代码为A.java

[java] view plaincopy
  1. publicclass A {  
  2.   
  3.        String s = "Unicode编码中文";  
  4.   
  5. }  


如果在GBK平台下编译,则报错信息如下:

A.java:1:错误编码GBK的不可映射字符

?......

如果要正确编译,则必须使用-encoding参数。例如:

Javac –encoding unicode A.java

8.   字节流和字符流

Java程序员都非常熟悉字节流和字符流,那么在使用这两种流的时候,要注意哪些编码相关的问题呢?

首先是,若程序并不需要编码解码时,尽量使用字节流。例如拷贝一个带有中文字符的文件,虽然其中有中文字符,但是拷贝文件并不需要编码解码,只需要将字节原封不动的拷贝过去即可,因此使用字节流即可。

其次,正如《java夜未眠》一书中提到的一样,转码只发生在Reader/InputStream的交界处和Writer/OutputStream的交界处,所以是有InputStreamReaderOutputStreamWriter两个类负责的。程序员需要在使用这两个类时小心处理编码与解码的类型。

9.   如何避免乱码

相信如果你已经认真读完此文,另外也读完了我推荐的《java夜未眠》中的“java繁体中文处理完全攻略”一节,那么你应该知道如何避免乱码了。

0 0
原创粉丝点击