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

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

1.   Byte开始

首先看一段代码:

 

      public static void startWithByte() {

              byte[] unicode_b = new byte[4];

              unicode_b[0] = (byte) 0XFE;

              unicode_b[1] = (byte) 0XFF;

              unicode_b[2] = (byte) 0X4E;

              unicode_b[3] = (byte) 0X2D;

              String unicode_str;

 

              byte[] gbk_b = new byte[2];

              gbk_b[0]=(byte) 0XD6;

              gbk_b[1]=(byte) 0XD0;

              String gbk_str;

              

              byte[] utf_b=new byte[3];

              utf_b[0]=(byte)0XE4;

              utf_b[1]=(byte)0XB8;

              utf_b[2]=(byte)0XAD;

              String utf_str;

              

              try {

                     unicode_str = new String(unicode_b, "unicode");

                     System.out.println("unicode string is:"+unicode_str);

                     

                     gbk_str = new String(gbk_b,"gbk");

                     System.out.println("gbk string is:"+gbk_str);

                     

                     utf_str=new String(utf_b,"utf-8");

                     System.out.println("utf-8 string is:"+utf_str);

                     

              } catch (UnsupportedEncodingException e) {

                     e.printStackTrace();

              }

       }

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

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位。看下面的程序:

      public static void byteAndChar() {

              char c1 = 0X4E2D;

              System.out.println(c1);

              char c2 = 20013;

              System.out.println(c2);

              byte[] b = new byte[2];

              b[0] = 0X4E;

              b[1] = 0x2D;

              String str = null;

              try {

                     str = new String(b, "unicode");

              } catch (UnsupportedEncodingException e) {

                     e.printStackTrace();

              }

              char c3 = str.charAt(0);

              System.out.println(c3);

              

              String str2="中a";

              System.out.println("length of str2 is :"+str2.length());

       }


运行程序可得:

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.推论是,用何种编码格式进行编码,就应该使用相同的格式进行解码。

看下面的例子:

     public static void stringExample() {

              String str = "中国ABC";

              try {

                     byte[] unicode_b = str.getBytes("unicode");

                     for (byte b : unicode_b) {

                            System.out.format("%02X ", b);

                     }

                     System.out.println();

                     String unicode_str = new String(unicode_b, "unicode");

                     System.out.println(unicode_str);

 

                     byte[] utf_b = str.getBytes("utf-8");

                     for (byte b : utf_b) {

                            System.out.format("%02X ", b);

                     }

                     System.out.println();

                     String utf_str = new String(utf_b, "utf-8");

                     System.out.println(utf_str);

 

                     byte[] gbk_b = str.getBytes("gbk");

                     for (byte b : gbk_b) {

                            System.out.format("%02X ", b);

                     }

                     System.out.println();

                     String gbk_str = new String(gbk_b, "gbk");

                     System.out.println(gbk_str);

              } catch (UnsupportedEncodingException e) {

                     e.printStackTrace();

              }

       }


运行结果:

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会默认使用什么编码呢?

看下面的程序:

      public static void getEncoding(){

               String encodeName = System.getProperty("file.encoding");

               System.out.println("System file encoding is " + encodeName);

       }


输出为:

System file encoding is UTF-8

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

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

6.   如何产生乱码

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

      public static void getEncodingError() {

              String str = "产生乱码";

              byte[] bs;

              try {

                     bs = str.getBytes("unicode");

                     String strError = new String(bs, "utf-8");

                     System.out.println(strError);

              } catch (UnsupportedEncodingException e) {

                     e.printStackTrace();

              }

       }


输出如下:

��Nu_Nqx_

7.   源文件的乱码问题

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

publicclass A {

       String s = "Unicode编码中文";

}


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

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

?......

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

Javac –encoding unicode A.java

8.   字节流和字符流

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

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

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

9.   如何避免乱码

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值