原文:http://blog.csdn.net/54powerman/article/details/77575656

作者:54powerman

一直以为,java中任意unicode字符串,可以使用任意字符集转为byte[]再转回来,只要不抛出异常就不会丢失数据,事实证明这是错的。

经过这个实例,也明白了为什么 getBytes()需要捕获异常,虽然有时候它也没有捕获到异常。

言归正传,先看一个实例。

用ISO-8859-1中转UTF-8数据

设想一个场景:

用户A,有一个UTF-8编码的字节流,通过一个接口传递给用户B;

用户B并不知道是什么字符集,他用ISO-8859-1来接收,保存;

在一定的处理流程处理后,把这个字节流交给用户C或者交还给用户A,他们都知道这是UTF-8,他们解码得到的数据,不会丢失。

下面代码验证:

 public static void main(String[] args) throws Exception {
//这是一个unicode字符串,与字符集无关
String str1 = "用户"; System.out.println("unicode字符串:"+str1); //将str转为UTF-8字节流
byte[] byteArray1=str1.getBytes("UTF-8");//这个很安全,UTF-8不会造成数据丢失 System.out.println(byteArray1.length);//打印6,没毛病 //下面交给另外一个人,他不知道这是UTF-8字节流,因此他当做ISO-8859-1处理 //将byteArray1当做一个普通的字节流,按照ISO-8859-1解码为一个unicode字符串
String str2=new String(byteArray1,"ISO-8859-1"); System.out.println("转成ISO-8859-1会乱码:"+str2); //将ISO-8859-1编码的unicode字符串转回为byte[]
byte[] byteArray2=str2.getBytes("ISO-8859-1");//不会丢失数据 //将字节流重新交回给用户A //重新用UTF-8解码
String str3=new String(byteArray2,"UTF-8"); System.out.println("数据没有丢失:"+str3);
}
输出: unicode字符串:用户
6
转成ISO-8859-1会乱码:用户
数据没有丢失:用户

用GBK中转UTF-8数据

重复前面的流程,将ISO-8859-1 用GBK替换。

只把中间一段改掉:

 //将byteArray1当做一个普通的字节流,按照GBK解码为一个unicode字符串
String str2=new String(byteArray1,"GBK"); System.out.println("转成GBK会乱码:"+str2); //将GBK编码的unicode字符串转回为byte[]
byte[] byteArray2=str2.getBytes("GBK");//数据会不会丢失呢?
运行结果: unicode字符串:用户
6
转成GBK会乱码:鐢ㄦ埛
数据没有丢失:用户

好像没有问题,这就是一个误区。

修改原文字符串重新测试

将两个汉字 “用户” 修改为三个汉字 “用户名” 重新测试。

ISO-8859-1测试结果:

 unicode字符串:用户名
9
转成GBK会乱码:用户名
数据没有丢失:用户名
GBK 测试结果: unicode字符串:用户名
9
转成GBK会乱码:鐢ㄦ埛鍚�
数据没有丢失:用户�?

结论出来了

ISO-8859-1 可以作为中间编码,不会导致数据丢失;

GBK 如果汉字数量为偶数,不会丢失数据,如果汉字数量为奇数,必定会丢失数据。

why?

为什么奇数个汉字GBK会出错

直接对比两种字符集和奇偶字数的情形

重新封装一下前面的逻辑,写一段代码来分析:

 public static void demo(String str) throws Exception {
System.out.println("原文:" + str); byte[] utfByte = str.getBytes("UTF-8");
System.out.print("utf Byte:");
printHex(utfByte);
String gbk = new String(utfByte, "GBK");//这里实际上把数据破坏了
System.out.println("to GBK:" + gbk); byte[] gbkByte=gbk.getBytes("GBK");
String utf = new String(gbkByte, "UTF-8");
System.out.print("gbk Byte:");
printHex(gbkByte);
System.out.println("revert UTF8:" + utf);
System.out.println("===");
// 如果gbk变成iso-8859-1就没问题
} public static void printHex(byte[] byteArray) {
StringBuffer sb = new StringBuffer();
for (byte b : byteArray) {
sb.append(Integer.toHexString((b >> 4) & 0xF));
sb.append(Integer.toHexString(b & 0xF));
sb.append("");
}
System.out.println(sb.toString());
}; public static void main(String[] args) throws Exception {
String str1 = "姓名";
String str2 = "用户名";
demo(str1,"UTF-8","ISO-8859-1");
demo(str2,"UTF-8","ISO-8859-1"); demo(str1,"UTF-8","GBK");
demo(str2,"UTF-8","GBK");
}
输出结果: 原文:姓名
UTF-8 Byte:e5 a7 93 e5 90 8d
to ISO-8859-1:姓名
ISO-8859-1 Byte:e5 a7 93 e5 90 8d
revert UTF-8:姓名
===
原文:用户名
UTF-8 Byte:e7 94 a8 e6 88 b7 e5 90 8d
to ISO-8859-1:用户名
ISO-8859-1 Byte:e7 94 a8 e6 88 b7 e5 90 8d
revert UTF-8:用户名
===
原文:姓名
UTF-8 Byte:e5 a7 93 e5 90 8d
to GBK:濮撳悕
GBK Byte:e5 a7 93 e5 90 8d
revert UTF-8:姓名
===
原文:用户名
UTF-8 Byte:e7 94 a8 e6 88 b7 e5 90 8d
to GBK:鐢ㄦ埛鍚�
GBK Byte:e7 94 a8 e6 88 b7 e5 90 3f
revert UTF-8:用户�?
===

为什么GBK会出错

前三段都没问题,最后一段,奇数个汉字的utf-8字节流转成GBK字符串,再转回来,前面一切正常,最后一个字节,变成了 “0x3f”,即”?”

我们使用”用户名” 三个字来分析,它的UTF-8 的字节流为:

[e7 94 a8] [e6 88 b7] [e5 90 8d]

我们按照三个字节一组分组,他被用户A当做一个整体交给用户B。

用户B由于不知道是什么字符集,他当做GBK处理,因为GBK是双字节编码,如下按照两两一组进行分组:

[e7 94] [a8 e6] [88 b7] [e5 90] [8d ?]

不够了,怎么办?它把 0x8d当做一个未知字符,用一个半角Ascii字符的 “?” 代替,变成了:

[e7 94] [a8 e6] [88 b7] [e5 90] 3f

数据被破坏了。

为什么 ISO-8859-1 没问题

因为 ISO-8859-1 是单字节编码,因此它的分组方案是:

[e7] [94] [a8] [e6] [88] [b7] [e5] [90] [8d]

因此中间不做任何操作,交回个用户A的时候,数据没有变化。

关于Unicode编码

因为UTF-16 区分大小端,严格讲:unicode==UTF16BE。

 public static void main(String[] args) throws Exception {
String str="测试";
printHex(str.getBytes("UNICODE"));
printHex(str.getBytes("UTF-16LE"));
printHex(str.getBytes("UTF-16BE"));
}
运行结果: fe ff 6d 4b 8b d5
4b 6d d5 8b
6d 4b 8b d5

其中 “fe ff” 为大端消息头,同理,小端消息头为 “ff fe”。

小结

作为中间转存方案,ISO-8859-1 是安全的。

UTF-8 字节流,用GBK字符集中转是不安全的;反过来也是同样的道理。

 byte[] utfByte = str.getBytes("UTF-8");
String gbk = new String(utfByte, "GBK");
这是错误的用法,虽然在ISO-8859-1时并没报错。 首先,byte[] utfByte = str.getBytes("UTF-8");
执行完成之后,utfByte 已经很明确,这是utf-8格式的字节流; 然后,gbk = new String(utfByte, "GBK"),
对utf-8的字节流使用gbk解码,这是不合规矩的。 就好比一个美国人说一段英语,让一个不懂英文又不会学舌的日本人听,然后传递消息给另一个美国人。 为什么ISO-8859-1 没问题呢? 因为它只认识一个一个的字节,就相当于是一个录音机。我管你说的什么鬼话连篇,过去直接播放就可以了。
getBytes() 是会丢失数据的操作,而且不一定会抛异常。 unicode是安全的,因为他是java使用的标准类型,跨平台无差异。

java中文GBK和UTF-8编码转换乱码的分析的更多相关文章

  1. Java用native2ascii命令做unicode编码转换

    背景:在做Java开发的时候,常常会出现一些乱码,或者无法正确识别或读取的文件,比如常见的validator验证用的消息资源(properties)文件就需要进行Unicode重新编码.原因是java ...

  2. gbk转utf-8 iconv 编码转换

    linux以下有时候 字符须要进行编码转换(爬虫将gbk转为utf-8编码...).一般能够选择iconv函数. 终端以下  输入 man 3 iconv 得到  iconv函数的用法. 个人看习惯了 ...

  3. java,arduino,C#之间的一些编码转换

    1.C#-> Encoding.UTF8.GetBytes( "abc中") ->[97,98,99,228,184,173] java->byte[] bs= ...

  4. ASP中Utf-8与Gb2312编码转换乱码问题的解决方法 页面编码声明

    ASP程序在同一个站点中,如果有UTF-8编码的程序,又有GB2312编码的程序时,在浏览UTF-8编码的页面后,再浏览当前网站GB2312的页面,GB2312编码的页面就会出现乱码 出现这样的问题是 ...

  5. Java:编码与乱码问题

    一.为什么要编码? 由于人类的语言太多,因而表示这些语言的符号太多,无法用计算机的一个基本的存储单元----byte来表示,因而必须要经过拆分或一些翻译工作,才能让计算机能理解. byte一个字节即8 ...

  6. Java编码与乱码问题

    一.为什么要编码? 由于人类的语言太多,因而表示这些语言的符号太多,无法用计算机的一个基本的存储单元----byte来表示,因而必须要经过拆分或一些翻译工作,才能让计算机能理解. byte一个字节即8 ...

  7. 补充:bytes类型以及字符编码转换

    内容转自小猿圈链接:https://book.apeland.cn/details/41/ 定义 bytes类型是指一堆字节的集合,在python中以b开头的字符串都是bytes类型 b'\xe5\x ...

  8. 【Java基础专题】编码与乱码(05)---GBK与UTF-8之间的转换

    原文出自:http://www.blogjava.net/pengpenglin/archive/2010/02/22/313669.html 在很多论坛.网上经常有网友问" 为什么我使用 ...

  9. JAVA中文字符串编码--GBK转UTF-8

    转载自:https://www.cnblogs.com/yoyotl/p/5979200.html 一.乱码的原因 gbk的中文编码是一个汉字用[2]个字节表示,例如汉字“内部”的gbk编码16进制的 ...

随机推荐

  1. 编译RocketMQ

    目前最新版本为 官方没有提供可执行的安装包,只提供了maven项目,去GitHub下载 点击Download ZIP 就可以了,如果你安装了版本控制工具 SVN 或者 GIT 就可以使用它上边的 UR ...

  2. VS 2010解决方案添加头文件和动态库

    右键点击项目,选择“properties”, Additional include references:头文件路径,分号隔开. Additional library directories:

  3. numpy之一些名称含义(持续更新)

    dtype:data type 数据类型 ndarray:n-dimension n维,ndarray:n维数组(多维数组) mean:平均值 std:standard deviation:标准差 p ...

  4. WCF开发实战系列五:创建WCF客户端程序

    WCF开发实战系列五:创建WCF客户端程序 (原创:灰灰虫的家http://hi.baidu.com/grayworm) 在前面的三篇文章中我们分别介绍了WCF服务的三种载体:IIS.Self-Hos ...

  5. python---web框架本质(1)

    总的来说php相对较为简单,但是内部封装太多,不利于对编程的更本质探索. 但是对于生产开发确实是一门不错的语言.python对于socket以及web框架的理解更加透彻 # coding:utf8 # ...

  6. Study 1 —— Python简介

    Python与其他语言的区别C\C++:学习成本高,学习周期长,偏系统底层,在开发硬件驱动.嵌入式.游戏引擎开发等领域有广泛的应用:JAVA:目前使用最广泛的编程语言,第一个跨平台运行的语言,在大型E ...

  7. Ubuntu(16.04.2)学习笔记(一)如何解决dpkg: error processing install-info

    一.服务器安装软件是出现以下的错误信息: www@TinywanAliYun:~$ sudo apt-get install letsencrypt Reading package lists... ...

  8. python 之禅 import this

    dongweiming的博客 前言 我这个博客一直都是一些技术分享,show code的地方,我从来没有写过个人生活或者情感杂谈,当然我也从来没有谈论过我对什么东西的喜恶. 很多人喜欢喷XX语言,喜欢 ...

  9. URL基本结构

    先来简单说下URI.URL.URN这三个鬼东西. URI全称Uniform Resource Identifier,统一资源标识符 URL全称Uniform Resource Locator,统一资源 ...

  10. Vue加载json文件

    一.在build/dev-server.js文件里 var app = express() 这句代码后面添加如下(旧版): var appData = require('../address.json ...