java各种处理字符串情况乱码原因分析及其解决方法

JAVA编解码

                          ---- 乱码问题

---- 通过一个事例进行分析

一、需求:

二、过程分析:

第一步:java文件编码格式

   文件格式非固定:

第二步:java文件编写保存

第三步:编译成Class文件

第四步:load class文件到JVM

第五步:内存

1、java文件中的字符串

2、运行时从网络中读取到内存中的字符串

3、运行时从本地文件中读取到内存中的字符串

4、运行时将内存中的字符串写入到文件中

通过一个事例进行分析:

一、需求:

    IDE:

          Myeclipse 10.0

    需求:

     编写一个java文件,用来读取网络资源、本地文件a以及java文件中的字符串。然后输出读取的字符串到文件b.txt中。

二、过程分析:

第一步:java文件编码格式

    文件格式非固定:

     Java文件在编写之前需要指定文件的编码格式,默认编码和当前操作系统平台编码保持一致。比如,当前操作系统平台为windows中文版,那么编码一般为GBK。当然可以对保存文件的编码进行修改。例如修改成UTF-8。那么此时文件保存的编码就为UTF-8。

第二步:java文件编写保存

  

该java文件中,有读取网络流的方法、读取本地文件的方法及其输出字符串到文件中的方法;编写完成之后,那么则以第一步的编码进行保存。另外,当前java文件中的所有字符串则以第一步中的编码得以保存。比如说当前java文件中有 String str=abc中国;第一步的编码设置为UTF-8,那么则以UTF-8进行保存。如果是GBK,那么则以GBK进行保存。

    第三步:编译成Class

  

   编译后的class文件的编码固定为UTF-8;和java文件编码格式无关。说明,编译器在编译的过程中将文件格式做了处理。编译器的这种处理操作不会带来乱码问题,因为我们必须要相信编译器的编解码处理过程。

    第四步:Load class 文件到jvm

   jvm中的所有字符串编码都为unicode。所以的话,从class文件再到jvm。编码又做了一次处理。同样也必须相信这种处理不会为乱码留下伏笔;

    第五步:内存

   内存中运行的是jvm中的数据,jvm中的数据编码为unicode。那么内存中同样也以unicode方式进行存储。但是有一个问题,内存运行的过程中,可能会设计到读取文件内容、网络内容以及输出这些内容的操作。在内存中读取的网络内容、文件内容会以不同的编码出现。这种编码和java文件中处理的方式有关。这儿是乱码问题出现原因的一部分。内存中还有输出字符串内容到文件的操作。这儿也会存在问题。

  详细来看:

  1、java文件中的字符串

Java代码 复制代码 收藏代码
  1. String abc = "abc中国"
  2. byte[] bytes = abc.getBytes(); 
  3. for (byte b : bytes) { 
  4.     System.out.print(b+"  "); 
  5. System.out.println(); 
  6. String newabc=new String(abc); 
  7. System.out.println(newabc);    
  8.      Java文件编码为UTF-8: 
  9.      打印结果: 
  10.      97  98  99  -28  -72  -83  -27  -101  -67   
  11.      abc中国 
  12.      Java文件编码为GBK: 
  13.      打印结果: 
  14.      97  98  99  -42  -48  -71  -6   
  15.      abc中国 
  16. 注:java文件的编码可以通过选择java文件右键Properties——》Text file encoding 中进行设置 
  String abc = "abc中国";
		byte[] bytes = abc.getBytes();
		for (byte b : bytes) {
			System.out.print(b+"  ");
		}
		System.out.println();
		String newabc=new String(abc);
		System.out.println(newabc);   
       Java文件编码为UTF-8:
       打印结果:
       97  98  99  -28  -72  -83  -27  -101  -67  
       abc中国
       Java文件编码为GBK:
       打印结果:
       97  98  99  -42  -48  -71  -6  
       abc中国
  注:java文件的编码可以通过选择java文件右键Properties——》Text file encoding 中进行设置

出现上面打印结果的原因分析如下;

   假如当前java文件的编码是gbk,那么"abc中国"则以gbk格式进行保存。

   abc.getBytes() 方法将字符串转换成字节处理方式是以当前平台的编码进行处理,而在选择java文件右键Properties——》Text file encoding 中进行设置的编码就是此时java文件平台的编码。

  abc.getBytes() 的本质是 abc.getBytes( Charset.defaultCharset())

它们两者是等效的 。因为我们当前的编码设置为gbk,那么就等效于abc.getBytes(gbk)。也就是说abc.getBytes()等价于abc.getBytes(gbk)

同样值得注意的是String new abc=new String(bytes); 这儿也进行了默认操作处理。String new abc=new String(bytes) 等效于String newabc=new String(bytes,Charset.defaultCharset()) 因为当前编码是gbk则

String newabc=new String(bytes) 等效于 String newabc=new String(bytes,gbk)

    理一下思路:

abc中国 以gbk编码保存——》以utf-8 编码的class文件存在——》以unicode编码load于jvm中——》同样以unicode的形式存在于内存中——》再以gbk编码转成字节——》最后以gbk编码转成字符串;

因为最后两步字符串转成字节和字节转成字符串的编码是统一的,都为gbk。所以不会有乱码的产生。乱码产生的原因就是最后两步字符串的编解码是不统一的。假如字符串变成字节的过程采用gbk编码,而最后字节变成字符串以utf-8的形式编码。那么肯定会出乱码问题。下面事例就是:

 

ReadJavaString.java

Java代码 复制代码 收藏代码
  1. String abc = "abc中国"
  2.     byte[] bytes = abc.getBytes("gbk"); 
  3.     for (byte b : bytes) { 
  4.         System.out.print(b+"  "); 
  5.     } 
  6.     System.out.println(); 
  7.     String newabc=new String(abc,"utf-8"); 
  8.     System.out.println(newabc);    
  9.  
  10.       打印结果如下: 
  11.       97  98  99  -42  -48  -71  -6   
  12.       abc?й? 
 String abc = "abc中国";
		byte[] bytes = abc.getBytes("gbk");
		for (byte b : bytes) {
			System.out.print(b+"  ");
		}
		System.out.println();
		String newabc=new String(abc,"utf-8");
		System.out.println(newabc);   

       打印结果如下:
       97  98  99  -42  -48  -71  -6  
       abc?й?

  结论:避免乱码出现问题的解决办法就是统一编码。

  字符串——字节     字节——字符串 用同一种编码

  2、运行时从网络中读取到内存中的字符串

      假如需求为:在远程服务器中保存着一个编码为gbk的 wsx.txt 文件,要将wsx.txt 文件中的内容读取到本地进行打印或者存储。wsx.txt 中的内容为abc中

ReadResourceFromNetWork.java

Java代码 复制代码 收藏代码
  1.      URL url =null
  2. try
  3.     url = new URL("http://wangshuxiong.jhost.cn/wsx.txt"); 
  4.     URLConnection urlconnection = url.openConnection(); 
  5.     InputStream ins = urlconnection.getInputStream(); 
  6.          int a=0
  7.     while ((a = ins.read()) != -1) { 
  8.         System.out.print(a+" "); 
  9.     } 
  10.     ins.close(); 
  11. } catch (Exception e) { } 
  12.  
  13.      打印结果:97 98 99 214 208  
  14.  
  15.      此时当前ReadResourceFromNetWork.java 文件的编码为gbk。改变ReadResourceFromNetWork.java 的编码为utf-8 的时候,我们发现一个现象。打印结果依旧为 :      97 98 99 214 208 
  16.      当我们改变wsx.txt 的编码为utf-8 ,内容依旧为“abc中” 不论ReadResourceFromNetWork.java文件的编码是utf-8 还是gbk,那么打印结果都为: 
  17.                97 98 99 228 184 173  
  18.     
  19. } catch (Exception e) { } 
  20.       
       URL url =null;
		try {
			url = new URL("http://wangshuxiong.jhost.cn/wsx.txt");
			URLConnection urlconnection = url.openConnection();
			InputStream ins = urlconnection.getInputStream();
           int a=0;
			while ((a = ins.read()) != -1) {
		    	System.out.print(a+" ");
			}
		    ins.close();
		} catch (Exception e) {	}

       打印结果:97 98 99 214 208 

       此时当前ReadResourceFromNetWork.java 文件的编码为gbk。改变ReadResourceFromNetWork.java 的编码为utf-8 的时候,我们发现一个现象。打印结果依旧为 :      97 98 99 214 208
       当我们改变wsx.txt 的编码为utf-8 ,内容依旧为“abc中” 不论ReadResourceFromNetWork.java文件的编码是utf-8 还是gbk,那么打印结果都为:
                 97 98 99 228 184 173 
     
		} catch (Exception e) {	}
	      

Java代码 复制代码 收藏代码
  1. 由此我们得出一个结论: 
  2.        从网络中读取资源文件的时候,无论当前java文件编码为何值,我们最后得到的一个个字节只与读取的资源文件保存的编码有关。 
  3.        那么我们可以知道的是:下面wsx.txt 编码为utf-8 的时候,那么读取的字节数组bytes 中的编码为utf-8; 
  4.        URL url =null
  5.         try
  6.             url = new URL("http://wangshuxiong.jhost.cn/wsx.txt"); 
  7.             URLConnection urlconnection = url.openConnection(); 
  8.             InputStream ins = urlconnection.getInputStream(); 
  9.            byte[] bytes=new byte[ins.available()]; 
  10.            int len= ins.read(bytes); /*返回的len是保存到bytes数组中实际的长度,比如说bytes数组定义长度为1024,但是只读取了100个字节长度,那么则返回的len为100,len最大值为bytes数组初始长度*/ 
  11.             ins.close(); 
  12.   当前ReadResourceFromNetWork.java编码为gbk的时候;调用下面方法的话肯定会是乱码,前面说过,String newabc=new String(bytes)  等效于String newabc=new String(bytes,Charset.defaultCharset());即为GBK编码,两者编码不统一,乱码是必然,打印结果如下: abc涓? 
由此我们得出一个结论:
       从网络中读取资源文件的时候,无论当前java文件编码为何值,我们最后得到的一个个字节只与读取的资源文件保存的编码有关。
       那么我们可以知道的是:下面wsx.txt 编码为utf-8 的时候,那么读取的字节数组bytes 中的编码为utf-8;
       URL url =null;
		try {
			url = new URL("http://wangshuxiong.jhost.cn/wsx.txt");
			URLConnection urlconnection = url.openConnection();
			InputStream ins = urlconnection.getInputStream();
           byte[] bytes=new byte[ins.available()];
           int len= ins.read(bytes); /*返回的len是保存到bytes数组中实际的长度,比如说bytes数组定义长度为1024,但是只读取了100个字节长度,那么则返回的len为100,len最大值为bytes数组初始长度*/
		    ins.close();
  当前ReadResourceFromNetWork.java编码为gbk的时候;调用下面方法的话肯定会是乱码,前面说过,String newabc=new String(bytes)  等效于String newabc=new String(bytes,Charset.defaultCharset());即为GBK编码,两者编码不统一,乱码是必然,打印结果如下: abc涓?

要解决上面乱码问题,方法很简单,不是说两者统一就行了嘛。既然读取字节的时候无法改变字节读取的编码,事实上也是万万不能改变的。那么我们就改变字节变成字符串时候的编码

String newabc=new String(bytes,utf-8);

打印结果如下:

abc中

小结一下:

对于读取网络资源乱码问题,如果能够知道资源的编码格式,那么,只需要在转成字符串的过程中使用这种编码就行。所以,关键问题落在了判断资源文件编码方式是那种。

有些文件是由BOM(byte order mark 字节序标记)的,那么我们只需要判断文件的BOM 就行。 比如说UTF-8 的BOM 是前三个字节为:-17、-69、-65GBK 则前两个字节为 -1  -2。所以一个文件含有ROM 的话,我们只需要判断字节序列标示就行。但是往往某些资源文件是没有字节序列标示符的。所以,就得考虑其他的方式解决了。

先看一些补充知识:

Unicode是字符集,全世界所有通用存在的文字都有一个唯一的标示符。

它的编码范围是0000-FFFF 。两个字节

各个国家语言的编码范围参照如下:

http://baike.baidu.com/view/40801.htm

中文的编码范围:4e00~9fff  大概有两万多个字。

Ascii的范围为0-127 。

GBK编码中,一个汉字用两个字节来标示,一个英文字符用一个字节表示,说白了就  是ascii值。

前一个字节十进制的范围是:128-254 ,第二个字节十进制范围是64-254

UTF-8编码中:一个汉字三个字节标示

第一个字节十进制范围: 224-255 ,第二、三个字节十进制 128-255

GBK 和 unicode的关系是存在一个键值对表保存gbk 十六进制和unicode十六进制的关系。然后通过unicode编码和中文对照关系,则可以通过一个gbk编码得到对应的中文汉字。

  流程如下:

       GBK-Unicode对应表                        Unicode表

通过gbk——获得unicode编码值——通过unicode编码值获得中文汉字

相关参考:http://www.chi2ko.com/jingyan/gbk2uni.htm

UTF-8 和Unicode的转换规则关系如下:

Unicode符号范围(十六进制)

UTF-8编码方式(二进制)

0000 - 007F

0xxxxxxx

0080 - 07FF

110xxxxx   10xxxxxx

0800 - FFFF

1110xxxx   10xxxxxx    10xxxxxx

因为中文的unicode范围为:4e00~9fff。所以中文是以三个字节来存储的。X是unicode对应二进制依次填充的

例如:Unicode 编码FFFF 二进制:1111  1111  1111  1111

填充的UTF-8 为:11101111  10111111  10111111

小结: gbk 与unicode之间的转换是通过gbk unicode映射表。

       Utf-8 与unicode之间的转换是通过转换规则公式

       所以说,unicode是核心中介。Gbk要转换成utf-8的话,先转成unicode。然后unicode再转换成utf-8;反之亦然。

继续接着讨论怎么判断读取资源文件格式的问题:

1、当读取的字节在0-127范围的话,说明是ascii字符。直接通过 char c=(char) ?转换就行 ;

2、如果字节 大于127 ,那么则判断字节是不是在128到224范围内,如果是的话,说明是GBK编码。因为utf-8的第一个字节范围是224到255范围内的。

3、如果不在128到224范围内,接着判断第二个字节,如果第二个字节在64到128范围内的话,那么则为GBK 编码。因为UTF-8 的第二三个字节范围是128--255.获取这两个字节,转成十六进制,再通过gbk unicode映射表就可以得到unicode值,再通过unicode值就可以得到中文汉字。

4、如果第二个字节依旧不在64到128范围内。那么则判断第三个字节。如果第三个字节在0--127范围的话,说明前两个字节为gbk编码。因为gbk编码是两个字节。

5、如果第三个字节大于127的话,说明这三个字节为utf-8编码。然后通过utf-8 与unicode的转码规则公式换算成unicode,然后通过unicode得到中文汉字。

  3、运行时从本地文件中读取到内存中的字符串

和网络中读取的结果完全一样,参考其上!

  4、运行时将内存中的字符串写入到文件中

   首先确保,读取到内存中的字符串正确,然后写入的话,一定要确保知道写入文件的保存编码,而不是按照默认的jvm运行编码进行保存。看事例!

Java代码 复制代码 收藏代码
  1. try
  2.             FileOutputStream fous = new FileOutputStream(new File("b.txt")); 
  3.             fous.write(value.getBytes("gbk")); 
  4.             fous.flush(); 
  5.             fous.close(); 
  6.         } catch (Exception e) {} 
  7. 上面的意思是将字符串按照gbk编码生成字节,然后写入到b.txt 文件中。事先,我已经设置了b.txt 的编码为gbk。这样就不会产生错误。因为我们打开b.txt文件看到文字的过程是:解析字节按照b.txt 保存的gbk格式进行解码成对应的unicode再到中文然后存于内存中。 
  8. 分析可知: 只要写出保存之前的字符串是正确的,那么以何种字节编码写入到文件中都是没有问题的,乱码的关键点在于,打开文件进行查看的过程中是以文件设置的保存编码格式进行转码的。 
try {
			FileOutputStream fous = new FileOutputStream(new File("b.txt"));
			fous.write(value.getBytes("gbk"));
			fous.flush();
			fous.close();
		} catch (Exception e) {}
上面的意思是将字符串按照gbk编码生成字节,然后写入到b.txt 文件中。事先,我已经设置了b.txt 的编码为gbk。这样就不会产生错误。因为我们打开b.txt文件看到文字的过程是:解析字节按照b.txt 保存的gbk格式进行解码成对应的unicode再到中文然后存于内存中。
分析可知: 只要写出保存之前的字符串是正确的,那么以何种字节编码写入到文件中都是没有问题的,乱码的关键点在于,打开文件进行查看的过程中是以文件设置的保存编码格式进行转码的。

申明,如需转载请注明出处!

< !--EndFragment-->

 

文章来源: http://1035054540-qq-com.iteye.com/blog/1856060

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值