作者:mindwind @云下山巅
"多少年来,乱码问题一直是程序员心中的痛。
多少程序员,在乱码中迷乱却不见字符集的偷笑。"
----------------------------------------------------------------------------------
今天一同事突然跑来找我,说我们之间的线上系统接口突然返回消息出现中文乱码了。
在此之前接口一直正常,但是代码在这期间我们都承认并没有作任何改动。
既然都说程序代码没有改动,那么我首先推测的必然是环境发生了改变。
咨询对方怎么处理接口返回的消息的,他们说进行了字符串字符集编码转换,于是让其粘贴代码来看看。
new String(msg.getBytes(), Charset.forName("GBK"));
于是看到了上面这段似曾相识的代码片段,这段代码是什么含义呢?
有人可能会说:“就是把 msg 字符串从其默认编码转换成 GBK 编码”。
这样想已经不是理解偏差,而是南辕北辙,大相迳庭了。
上面那行代码用到了 String 类的两个 api:
publicbyte[] getBytes()
public String(byte bytes[], Charset charset)
String 类说是 java 中使用最频繁,最常用的类,应该毫无争议吧。
但可能很多人并没仔细看过这个最熟悉类的java doc。
getBytes()的 java doc 如下描述:
Encodes thisStringinto a sequence of bytes using the platform's default charset, storing the result into a new byte array.
意思是将该字符串使用平台默认字符集编码为一个字节序列并存储于返回的字节数组中。
而另一个构造函数 api 的 java doc 则说:
Constructs a newStringby decoding the specified array of bytes using the specifiedcharset.
意思是使用指定的字符集解码字节数组来构造一个新的字符串。
通过 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字符集和编码之前,我们先了解一下为什么在Java编程中需要关注字符集和编码。Java作为一种广泛应用的编程语言,其内部采用的是Unicode编码,这使得Java能够很好地...
### Java支持的字符集 Java作为一种广泛使用的编程语言,在处理多语言环境下的文本时,其对字符集的支持显得尤为重要。...开发者可以根据实际需求选择合适的字符集进行字符串处理,确保数据的正确性和完整性。
java 字符集编码转换,时间格式化,数字判断等,java文件
### Java字符集编码问题详解 #### 一、引言 在Java编程中,字符集编码问题是一个常见且重要的议题。由于不同的系统、平台以及网络环境中可能存在多种字符编码格式,这导致了在处理文本数据时可能会遇到编码不一致...
JAVA及相关字符集编码问题 在深入探讨JAVA与字符集编码问题之前,我们首先需要理解不同字符集编码的基本概念以及它们在JAVA环境中的应用。字符集编码是计算机系统中表示文字的一种方式,它决定了如何将字符转换为二...
在Java编程语言中,字符集(Charset)是用于表示文本数据的一系列规则,它定义了字符与二进制数据之间的映射关系。字符集的解码是将字节流转换为字符流的过程,通常涉及到从特定编码格式如UTF-8、GBK等还原出原始的...
Java字符集是一个涵盖编码基础知识、Java编程环境与字符编码关系以及不同编码标准如何在Java中应用的主题。在本文中,我们将深入探讨这些方面,以便更好地理解Java如何处理各种字符编码。 首先,我们要明白编码的...
在Java编程中,正确地处理文件的字符集编码至关重要,特别是在读取或写入含有非ASCII字符(如中文、日文、韩文等)的文件时。`cpdetector`是Java中一个常用的库,用于自动检测文件的字符集编码。这个库能够帮助...
Java字符集基础知识与问题 字符集编码是计算机处理文本数据的基础,不同的编码方式适用于不同的应用场景。本文主要讨论编码的基本知识,特别是与Java相关的部分,包括ISO8859-1、GB2312/GBK、Unicode以及UTF编码。...
Java字符集是Java编程语言中处理字符编码的基础概念,它对于理解如何在程序中正确地存储、处理和传输文本至关重要。在Java中,字符集主要指的是Unicode字符集,特别是其子集UTF-8,它是Java默认使用的字符编码。Java...
本文将深入探讨Java中的输入输出流以及字符集的相关知识点。 一、Java IO流概述 Java的IO流模型是基于管道的概念,数据在不同设备之间流动就像水流在管道中传输一样。流可以分为四类:字节流(Byte Stream)和字符...
Java字符集是编程中至关重要的概念,涉及到计算机中字符的表示和中文字符的编码问题。在Java中,字符集是用来定义字符的二进制代码集合,它允许程序处理各种语言的文字。 首先,我们来理解计算机中字符的表示。...
在Java中,处理不同字符集之间的字符串转换是一项常见任务。尤其是在处理国际化应用时,理解并掌握各种字符编码格式变得尤为重要。下面将介绍几种常见的字符编码格式以及如何在Java中实现它们之间的转换。 #### 1. ...
java 字符串转16进制 16进制转字符串 将两个ASCII字符合成一个字节; java 字符串转16进制 16进制转字符串 将两个ASCII字符合成一个字节; java 字符串转16进制 16进制转字符串 将两个ASCII字符合成一个字节; java ...
Java字符集编码应用探讨
java 字符串工具类 java 字符串工具类java 字符串工具类 java 字符串工具类java 字符串工具类 java 字符串工具类java 字符串工具类 java 字符串工具类java 字符串工具类 java 字符串工具类java 字符串工具类 java ...
本文主要包括以下几个方面:编码基本知识,java,系统软件,url,工具软件等。
这是因为 Java 在处理字符串时默认按照特定的字符集进行解码,而在本例中,Java 应用期望使用 `ZHS16GBK` 解码,但实际上却按照 `US7ASCII` 编码的数据进行了解码,从而导致乱码现象的发生。 #### 解决方案 为了...