`

转:java字符集的欺骗性

 
阅读更多
http://www.csdn123.com/html/blogs/20130419/4302.htm
java字符集的欺骗性

返回脚本百事通
"多少年来,乱码问题一直是程序员心中的痛。

多少程序员,在乱码中迷乱却不见字符集的偷笑。"



----------------------------------------------------------------------------------





今天一同事突然跑来找我,说我们之间的线上系统接口突然返回消息出现中文乱码了。

在此之前接口一直正常,但是代码在这期间我们都承认并没有作任何改动。

既然都说程序代码没有改动,那么我首先推测的必然是环境发生了改变。

咨询对方怎么处理接口返回的消息的,他们说进行了字符串字符集编码转换,于是让其粘贴代码来看看。



new String(msg.getBytes(), Charset.forName("GBK"));



于是看到了上面这段似曾相识的代码片段,这段代码是什么含义呢?

有人可能会说:“就是把 msg 字符串从其默认编码转换成 GBK 编码”。

这样想已经不是理解偏差,而是南辕北辙,大相迳庭了。





上面那行代码用到了 String 类的两个 api:



public byte[] getBytes()

public String(byte bytes[], Charset charset)



String 类说是 java 中使用最频繁,最常用的类,应该毫无争议吧。

但可能很多人并没仔细看过这个最熟悉类的java doc。

getBytes()的 java doc 如下描述:



Encodes this String into a sequence of bytes using the platform's default charset, storing the result into a new byte array.



意思是将该字符串使用平台默认字符集编码为一个字节序列并存储于返回的字节数组中。

而另一个构造函数 api 的 java doc 则说:



Constructs a new String by decoding the specified array of bytes using the specified charset.



意思是使用指定的字符集解码字节数组来构造一个新的字符串。



通过 java doc 的说明,我们明白了这个构造函数指定的字符集是用于解码第一个传入参数字节数组的,而不是表达用这个字符集构造一个新的字符串(噢哦,不知道还有多少人被这个构造函数的签名欺骗误导了)。

那么上面的那行代码其实在表达什么意思呢,这里就呼之欲出了。

它将 msg 字符串采用平台默认编码为字节数组,再基于此字节数组通过 GBK 进行解码来构造一个新的字符串。

那么这个过程中发生了什么事情,字符串编码如我们所想从平台默认变为 GBK?如果这样想就真是很傻很天真了。

其实上面代码的执行过程,如果平台默认编码就是 GBK,那么这个代码就是创建了一个新字符串和原来 msg 相等,没副作用,干点无用功。

前文提及的接口乱码的问题正是因为,原先对方平台编码为 GBK,昨天突然改变为 UTF-8 后导致的乱码。





而程序原作者期待的转换编码过程其实完全没有必要,因为 java 中字符串的编码统一采用了 unicode 字符集。

而 String 对象的构造过程,就是将指定字符集编码的字节数组转换为 unicode 编码的字符串。

在 java 中说什么 GBK 编码的字符串,ISO-8859-1 编码的字符串都是无稽之谈。

那么我们经常在 web 开发中使用的一种转码方式,也是一段熟悉的代码可能又会使某些人陷入困惑了,如下:



new String(request.getParameter("xxxx").getBytes("iso-8859-1"),"utf-8")



上面这段代码在基于 tomcat 开发的一些 web 应用中进行转码有效,这又是为什么?

其实原因是从浏览器传入的请求参数一般默认使用的 utf-8 编码,而 tomcat 接收到请求后将参数采用默认的 iso-8859-1 解码生成了一个错误的字符串参数。

要恢复就得先把字符串恢复为原始字节数组,再通过 utf-8 来解码生成正确得字符串,所以才有如上写法。


而如上写法,对于不明白浑浑噩噩的程序员,就误认为将 iso-8859-1 的字符串转换为了 utf-8 格式的字符串。(这也没什么好羞耻的,我也这样以为了好些年:)





但字符集编码转换可不是负负得正那么简单,我们之所以能正确的转换回来,是因为 iso-8859-1 的字符集是单字节字符集编码的。

下面举个简单例子来说明下字符集编码转换的问题。


// 单字节编码字符集
Charset iso88591 = Charset.forName("iso-8859-1");
// 双字节编码字符集
Charset big5 = Charset.forName("big5");
// 可变长度编码字符集
Charset utf8 = Charset.forName("utf-8");


// 原始字符串
String src = "测试";               
// utf8 编码的字节数组
byte[] utf8Bytes = src.getBytes(utf8);   
// 使用 iso-8859-1 错误解码的字符串(乱码)
String wrongStr = new String(utf8Bytes, iso88591);  
// 使用 big5 错误解码的字符串(还是乱码)
String wrongStr2 = new String(utf8Bytes, big5);


System.out.println("wrongStr-iso88591-decoding = " + wrongStr + "    len=" + wrongStr.length());
System.out.println("wrongStr-big5-decoding     = " + wrongStr2 + "   len=" + wrongStr2.length());
System.out.println("orignal-utf8-bytes         = " + Arrays.toString(utf8Bytes));


// 把 iso-8859-1 错误解码的字符串恢复utf8编码的字节数组 - 可逆
byte[] resumeBytes = wrongStr.getBytes(iso88591);  
String rightStr = new String(resumeBytes, utf8);


// 把 big5 错误解码的字符串恢复utf8编码的字节数组 - 不可逆
byte[] resumeBytes2 = wrongStr2.getBytes(big5);
String rightStr2 = new String(resumeBytes2, utf8);


System.out.println("resume-iso88591-utf8-bytes = " + Arrays.toString(resumeBytes));
System.out.println("resume-big5-utf8-bytes     = " + Arrays.toString(resumeBytes2));
System.out.println(rightStr);
System.out.println(rightStr2);



原始字符串 src,首先编码为 utf-8 的字节数组。

用 iso-8859-1 单字节字符集解码得到一个错误的字符串 wrongStr。

用 big5 双字节字符集解码得到一个错误的字符串 wrongStr2。

对两个错误的(乱码)字符串调用 getBytes(charset) 尝试还原为原始字节数组,再使用正确的编码 utf-8 解码生成正确的字符串。

可以看到如下输出结果:



wrongStr-iso88591-decoding = ??è?   len=6
wrongStr-big5-decoding     = 瘚??   len=3
orignal-utf8-bytes         = [-26, -75, -117, -24, -81, -107]
resume-iso88591-utf8-bytes = [-26, -75, -117, -24, -81, -107]
resume-big5-utf8-bytes     = [-26, -75, 63, 63]
测试
???



中文 “测试” 两个字,采用 utf-8 编码,这两个字正好每个编码为 3 个字节,一共6个字节。

iso-8859-1 属于单字节字符集,解码生成的字符串字符和字节一一对应,因此解码后的乱码字符串 wrongStr 长度为 6。

而 big5 属于双字节字符集,解码生成的字符串每 2 个字节对应一个字符,因此解码后的乱码字符串 wrongStr2 长度为 3。

如上,[-26, -75] 字节组合解码为 big5 字符[瘚],而字节组合 [-117, -24] 和 [-81, -107] 在 big5 中没有对应的字符,因此被解码为[??]。

当从一个大字符集字节序列解码为一个小字符集字符串时,可能会产生解码丢失,并且不可逆转,因此如上随后我们用 big5 在编码回来时已经不是原来的字节序列了。





最后,我们在处理 java 编码问题时要区分清楚几个概念

1. java 字符串,内部统一采用 unicode 字符集

2. java 字符串之间不存在字符集转换,只有字节序列之间才存在字符集转换



补充说明:

unicode 是一种符号集,规定了符号的二进制代码,却没规定如何存储,现在的规模容纳了100多万种符号

unicode 的不同存储方式,代表了 unicode 的多种实现方式。

utf-8 只是 unicode 的一种实现方式,类似的还有 utf-16, utf-32

java 的字符串 和 jvm 运行时都是采用 utf-16 格式。


分享到:
评论

相关推荐

    java字符集编码乱码详解

    ### Java字符集编码乱码详解 #### 一、编码与乱码基础知识 在计算机科学领域,字符集(Character Set)是指一系列符号和电子通信代码的标准集合。每种字符集都有其特定的应用场景和优势。例如,ASCII(American ...

    java.sql.SQLException: 不支持的字符集 (在类路径中添加 orai18n.jar): ZHS16GBK

    java.sql.SQLException: 不支持的字符集 (在类路径中添加 orai18n.jar): ZHS16GBK ……

    语言程序设计资料:java字符编码.doc

    语言程序设计资料:java字符编码.doc

    Java字符集和编码

    ### Java字符集和编码 #### 一、引言 在探讨Java字符集和编码之前,我们先了解一下为什么在Java编程中需要关注字符集和编码。Java作为一种广泛应用的编程语言,其内部采用的是Unicode编码,这使得Java能够很好地...

    java 字符串转16进制 16进制转字符串 将两个ASCII字符合成一个字节;

    java 字符串转16进制 16进制转字符串 将两个ASCII字符合成一个字节; java 字符串转16进制 16进制转字符串 将两个ASCII字符合成一个字节; java 字符串转16进制 16进制转字符串 将两个ASCII字符合成一个字节; java ...

    java字符集

    ### Java字符集详解 #### 一、概述与背景 本文主要探讨了字符编码的基本概念以及Java编程语言如何处理不同字符集。随着信息技术的发展,字符编码技术也在不断演进,以支持全球范围内各种语言的文本表示需求。文章...

    JAVA及相关字符集编码问题

    JAVA及相关字符集编码问题 在深入探讨JAVA与字符集编码问题之前,我们首先需要理解不同字符集编码的基本概念以及它们在JAVA环境中的应用。字符集编码是计算机系统中表示文字的一种方式,它决定了如何将字符转换为二...

    java字符集编码问题

    ### Java字符集编码问题详解 #### 一、引言 在Java编程中,字符集编码问题是一个常见且重要的议题。由于不同的系统、平台以及网络环境中可能存在多种字符编码格式,这导致了在处理文本数据时可能会遇到编码不一致...

    java 字符集的解码方法

    `BufferedReader`则可以提供更高效的读取操作,它可以从`InputStreamReader`中读取字符: ```java BufferedReader bufferedReader = new BufferedReader(reader); String line; while ((line = bufferedReader....

    Java Unicode 和字符集

    ### Java中的Unicode与字符集详解 #### 一、引言 在软件开发过程中,正确处理文本数据至关重要。尤其是在全球化日益加深的今天,软件不仅要能够处理英语等常见的西方语言,还要支持世界各地的语言,包括中文、日文...

    txt文本文件字符集格式转换(GBK等格式转UTF8),class

    txt文本文件字符集格式转换(GBK等格式转UTF8) 需要java环境(也就是要求本机安装有jdk并配置好环境变量), 执行语句格式:java txttoutf test.txt test8888.txt 其中txttoutf是java类名(也是程序文件), test.txt...

    txt文本文件字符集格式转换(GBK等格式转UTF8),java语句

    txt文本文件字符集格式转换(GBK等格式转UTF),java语句开发,运行需要java环境, DOS下运行命令:java txttoutf test.txt test8888.txt(test.txt是原始文件,test8888.txt是处理后UTF8格式的txt文件)

    java 字符串转16进制Ascii

    总结一下,Java中字符串转16进制ASCII涉及的关键点有: 1. `char`类型的字符与ASCII码的转换。 2. 使用`Integer.toHexString()`将ASCII码转换为16进制字符串。 3. 处理Unicode字符时,需要考虑字符集和编码方式。 ...

    JAVA字符集

    Java字符集是一个涵盖编码基础知识、Java编程环境与字符编码关系以及不同编码标准如何在Java中应用的主题。在本文中,我们将深入探讨这些方面,以便更好地理解Java如何处理各种字符编码。 首先,我们要明白编码的...

    java文件字符编码检测和转换

    字符编码检测和转换 附件中:FileEncodeDetector.java 此文件可以检测指定文件的编码格式 public static String getFileEncode(File file) {...} 附件中:FileCharsetConverter.java 此文件可以实现两个编码的相互...

    Java字符集[定义].pdf

    Java的`java.nio.charset.Charset`类是字符集的核心,它提供了对不同字符集的支持。通过`Charset.forName("charsetName")`可以获取特定字符集的实例。 5. 编码转换 `java.nio.charset.CharsetEncoder`和`...

    json_lib使用实例:java对象与json字符串的互转

    1. **JSON字符串转Java对象** 使用`json-lib`,我们可以将JSON字符串解析为JavaBean、HashMap、ArrayList等。例如,假设我们有一个JSON字符串`{"name":"John", "age":30}`,可以这样转换: ```java String json...

    JAVA日期与字符串的转换

    ### JAVA日期与字符串的转换 在Java编程中,经常需要将日期对象转化为字符串形式以便于存储或显示,或者反过来将字符串转化成日期对象来进行日期计算等操作。本文将详细介绍如何在Java中实现这两种转换。 #### 一...

    jdbc连接oracle字符集不同出现乱码

    这是因为 Java 在处理字符串时默认按照特定的字符集进行解码,而在本例中,Java 应用期望使用 `ZHS16GBK` 解码,但实际上却按照 `US7ASCII` 编码的数据进行了解码,从而导致乱码现象的发生。 #### 解决方案 为了...

    java字符集基础知识及问题

    Java字符集基础知识与问题 字符集编码是计算机处理文本数据的基础,不同的编码方式适用于不同的应用场景。本文主要讨论编码的基本知识,特别是与Java相关的部分,包括ISO8859-1、GB2312/GBK、Unicode以及UTF编码。...

Global site tag (gtag.js) - Google Analytics