java中的編碼轉(zhuǎn)換過(guò)程(以u(píng)tf8和gbk為例)
java中的編碼轉(zhuǎn)換(以u(píng)tf8和gbk為例)
在正常javaweb開(kāi)發(fā)中經(jīng)常會(huì)發(fā)現(xiàn)字符轉(zhuǎn)換的需求,會(huì)存在中文字符轉(zhuǎn)換亂碼的現(xiàn)象,如何解決以及其轉(zhuǎn)換原理我至今懵懵懂懂,于是專門寫了個(gè)測(cè)試代碼進(jìn)行嘗試,總算理清了編碼,先上結(jié)論,總結(jié)如下:
utf8中存放有各種語(yǔ)言編碼,當(dāng)前主流開(kāi)發(fā)中會(huì)使用utf8進(jìn)行編碼解碼,該方式不會(huì)產(chǎn)生亂碼,產(chǎn)生亂碼有以下幾種情況
- 1、gbk(中文)、iso-8859-1(無(wú)中文)等其他方式進(jìn)行編碼,則只能用其對(duì)應(yīng)方式進(jìn)行解碼,否則為亂碼
- 2、使用utf8進(jìn)行編碼用其他方式解碼則會(huì)導(dǎo)致亂碼,需進(jìn)行一次轉(zhuǎn)換
- 3、使用無(wú)對(duì)應(yīng)字符(中文)的字符集(iso-8859-1)編碼會(huì)導(dǎo)致亂碼,且無(wú)法還原解碼
以下是針對(duì)以上情況的代碼測(cè)試
1.如何編碼就如何解碼
/**
?* 測(cè)試編碼轉(zhuǎn)換 中文 => utf-8 編碼 - 解碼
?*/
@Test
public void test0() {
? ? String test = "測(cè)試";
? ? System.out.println(Arrays.toString(test.getBytes(StandardCharsets.UTF_8)));//[-26, -75, -117, -24, -81, -107]
? ? System.out.println(new String(test.getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8));//測(cè)試
}/**
?* 測(cè)試編碼轉(zhuǎn)換 中文 => gbk 編碼 - 解碼
?*/
@Test
public void test1() throws UnsupportedEncodingException {
? ? String test = "測(cè)試";
? ? System.out.println(Arrays.toString(test.getBytes("gbk")));//[-78, -30, -54, -44]
? ? System.out.println(new String(test.getBytes("gbk"), "GBK"));//測(cè)試
}utf8編碼 - 錯(cuò)誤形式解碼
/**
?* 測(cè)試編碼轉(zhuǎn)換 中文 => utf-8 編碼- gbk解碼
?*/
@Test
public void test2() throws UnsupportedEncodingException {
? ? String test = "測(cè)試";
? ? System.out.println(Arrays.toString(test.getBytes(StandardCharsets.UTF_8)));//[-26, -75, -117, -24, -81, -107]
? ? System.out.println(new String(test.getBytes(StandardCharsets.UTF_8), "gbk"));//嫻嬭瘯
}正確做法,按錯(cuò)誤的解碼形式(gbk)作為中轉(zhuǎn),將其按錯(cuò)誤形式(gbk)重新還原編碼(utf8-encode),再使用utf8進(jìn)行一次正確解碼(utf8-decode)即可得到原來(lái)的字符
/**
?* 測(cè)試編碼轉(zhuǎn)換 中文 => utf-8 編碼 - gbk 解碼 ===> gbk 編碼 - utf-8解碼
?* "測(cè)試" => (utf8-encode)[-26, -75, -117, -24, -81, -107] => (gbk-decode)嫻嬭瘯
?* "嫻嬭瘯" => (utf8-encode)[-26, -75, -117, -24, -81, -107] => (utf8-decode)"測(cè)試"
?*/
@Test
public void test3() throws UnsupportedEncodingException {
? ? String test = "測(cè)試";
? ? String test_gbk_utf8 = new String(test.getBytes(StandardCharsets.UTF_8), "gbk");
? ? System.out.println(test_gbk_utf8);//嫻嬭瘯
? ? String test_utf8_gbk = new String(test_gbk_utf8.getBytes("gbk"), StandardCharsets.UTF_8);
? ? System.out.println(test_utf8_gbk);//測(cè)試
}3.無(wú)對(duì)應(yīng)字符編碼
@Test
? ? public void test4() throws UnsupportedEncodingException {
? ? ? ? String test = "測(cè)試";
? ? ? ? System.out.println(Arrays.toString(test.getBytes(StandardCharsets.ISO_8859_1)));//[63, 63]
? ? ? ? System.out.println(new String(test.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.ISO_8859_1));//??
? ? }該情況下即使使用原先的編碼方式進(jìn)行解碼也無(wú)法還原字符了,屬于不可逆的狀態(tài)
java編碼格式的轉(zhuǎn)換以及亂碼恢復(fù)
如何在java中進(jìn)行編碼格式轉(zhuǎn)換
下面這行代碼的含義是: 獲取目標(biāo)字符串str的gbk編碼格式的二進(jìn)制碼,然后將二進(jìn)制碼按照utf8編碼格式重新編碼成字符串,當(dāng)然,下面這種寫法百分百會(huì)亂碼,因?yàn)榫幋a格式不一致.
new String(str.getBytes("gbk"),"utf8")首先什么情況會(huì)亂碼
如果要傳輸一個(gè)字符串,首先要按照一定的編碼格式將字符串轉(zhuǎn)換成字節(jié)流,當(dāng)字節(jié)流傳輸?shù)浇邮辗降臅r(shí)候再將字節(jié)流按照某種編碼格式轉(zhuǎn)換成字符串.亂碼也正是產(chǎn)生在重新轉(zhuǎn)換成字符串的過(guò)程中.以下是我對(duì)中文亂碼的測(cè)試:
? String str="彩虹";
? ? ? ? String [] a=new String[] {"gbk","unicode","utf8","gb2312"};
? ? ? ? for (int i=0;i<a.length;i++){
? ? ? ? ? ? for (int j=0;j<a.length;j++){
? ? ? ? ? ? ? ? System.out.println("二進(jìn)制格式: ? "+a[i]+"編碼格式: ?"+a[j]);
? ? ? ? ? ? ? ? System.out.println("編碼后的字符串: ?"+new String(str.getBytes(a[i]),a[j]));
?
? ? ? ? ? ? }
? ? ? ? }二進(jìn)制格式: gbk編碼格式: gbk
編碼后的字符串: 彩虹
二進(jìn)制格式: gbk編碼格式: unicode
編碼后的字符串: ??
二進(jìn)制格式: gbk編碼格式: utf8
編碼后的字符串: ???
二進(jìn)制格式: gbk編碼格式: gb2312
編碼后的字符串: 彩虹
二進(jìn)制格式: unicode編碼格式: gbk
編碼后的字符串: ?_i唝
二進(jìn)制格式: unicode編碼格式: unicode
編碼后的字符串: 彩虹
二進(jìn)制格式: unicode編碼格式: utf8
編碼后的字符串: ??_i?y
二進(jìn)制格式: unicode編碼格式: gb2312
編碼后的字符串: ??_i?y
二進(jìn)制格式: utf8編碼格式: gbk
編碼后的字符串: 褰╄櫣
二進(jìn)制格式: utf8編碼格式: unicode
編碼后的字符串: ?馹
二進(jìn)制格式: utf8編碼格式: utf8
編碼后的字符串: 彩虹
二進(jìn)制格式: utf8編碼格式: gb2312
編碼后的字符串: 褰╄??
二進(jìn)制格式: gb2312編碼格式: gbk
編碼后的字符串: 彩虹
二進(jìn)制格式: gb2312編碼格式: unicode
編碼后的字符串: ??
二進(jìn)制格式: gb2312編碼格式: utf8
編碼后的字符串: ???
二進(jìn)制格式: gb2312編碼格式: gb2312
編碼后的字符串: 彩虹
可以看出,如果二進(jìn)制編碼格式和字符串的編碼格式不同就會(huì)引起亂碼.
為什么gbk,gb2312轉(zhuǎn)換沒(méi)有亂碼?
gbk和gb2312之間的轉(zhuǎn)換沒(méi)有亂碼是因?yàn)間bk是gb2312的增強(qiáng)版本,支持更多的漢字編碼,所以如果二進(jìn)制編碼格式是gbk而解碼格式是gb2312,這種情況是有可能出現(xiàn)部分漢字亂碼的.
亂碼的數(shù)據(jù)可以轉(zhuǎn)變回來(lái)嗎?
上述結(jié)果中的亂碼其實(shí)可以大致分為兩種,一種是復(fù)雜的漢字和圖形組合,一種是"?".
如果希望恢復(fù)的亂碼數(shù)據(jù)中有問(wèn)號(hào),那么這條數(shù)據(jù)恢復(fù)的可能性就不大了.因?yàn)槌?quot;?"的其他亂碼其實(shí)都是有自己的編碼規(guī)則的,只要逆向的解碼并按照正確的編碼格式重新編碼就可以恢復(fù).但是"?"除外,因?yàn)楫?dāng)字節(jié)流按照某種編碼格式重新編譯的時(shí)候,字節(jié)數(shù)據(jù)中無(wú)法按照該編碼格式轉(zhuǎn)換成有意義字符的字節(jié)都會(huì)轉(zhuǎn)換成"?",所以就算逆向的編碼成字節(jié)流,所有的"?"都會(huì)轉(zhuǎn)換成同一字節(jié),也就失去了他本身的意義.
如果亂碼中不包含"?",那么還是有希望轉(zhuǎn)換回去的,我以上述亂碼中的 "褰╄櫣" 為例重新進(jìn)行了一次轉(zhuǎn)換,代碼如下:
? ? ? ?String str="褰╄櫣";
? ? ? ? String [] charset=new String[] {"gbk","unicode","utf8","gb2312"};
? ? ? ? for (int i=0;i<charset.length;i++){
? ? ? ? ? ? for (int j=0;j<charset.length;j++){
? ? ? ? ? ? ? ? System.out.println("二進(jìn)制格式: ? "+charset[i]+"編碼格式: ?"+charset[j]);
? ? ? ? ? ? ? ? System.out.println("編碼后的字符串: ?"+new String(str.getBytes(charset[i]),charset[j]));
?
? ? ? ? ? ? }
? ? ? ? }二進(jìn)制格式: gbk編碼格式: gbk
編碼后的字符串: 褰╄櫣
二進(jìn)制格式: gbk編碼格式: unicode
編碼后的字符串: ?馹
二進(jìn)制格式: gbk編碼格式: utf8
編碼后的字符串: 彩虹
二進(jìn)制格式: gbk編碼格式: gb2312
編碼后的字符串: 褰╄??
二進(jìn)制格式: unicode編碼格式: gbk
編碼后的字符串: ??0%Dj?
二進(jìn)制格式: unicode編碼格式: unicode
編碼后的字符串: 褰╄櫣
二進(jìn)制格式: unicode編碼格式: utf8
編碼后的字符串: ???0%Dj?
二進(jìn)制格式: unicode編碼格式: gb2312
編碼后的字符串: ???0%Dj?
二進(jìn)制格式: utf8編碼格式: gbk
編碼后的字符串: 瑜扳晞婭?
二進(jìn)制格式: utf8編碼格式: unicode
編碼后的字符串: ??閄?
二進(jìn)制格式: utf8編碼格式: utf8
編碼后的字符串: 褰╄櫣
二進(jìn)制格式: utf8編碼格式: gb2312
編碼后的字符串: 瑜扳??婭?
二進(jìn)制格式: gb2312編碼格式: gbk
編碼后的字符串: 褰╄?
二進(jìn)制格式: gb2312編碼格式: unicode
編碼后的字符串: ??
二進(jìn)制格式: gb2312編碼格式: utf8
編碼后的字符串: 彩??
二進(jìn)制格式: gb2312編碼格式: gb2312
編碼后的字符串: 褰╄?
可以看到 其中一種轉(zhuǎn)換方式成功的將亂碼轉(zhuǎn)變回了正常的中文漢字
二進(jìn)制格式: gbk編碼格式: utf8
編碼后的字符串: 彩虹
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
聊聊Java 成員變量賦值和構(gòu)造方法誰(shuí)先執(zhí)行的問(wèn)題
這篇文章主要介紹了聊聊Java 成員變量賦值和構(gòu)造方法誰(shuí)先執(zhí)行的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-10-10
Java C++題解leetcode816模糊坐標(biāo)示例
這篇文章主要為大家介紹了Java C++題解leetcode816模糊坐標(biāo)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01
SpringMVC xml文件路徑在web.xml中的配置方式
這篇文章主要介紹了SpringMVC xml文件路徑在web.xml中的配置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09
四步輕松搞定java web每天定時(shí)執(zhí)行任務(wù)
本篇文章主要介紹了四步輕松搞定java web每天定時(shí)執(zhí)行任務(wù),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-01-01
java List.of()與Arrays.asList()方法對(duì)比分析
這篇文章主要為大家介紹了java List.of()與Arrays.asList()方法對(duì)比分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11
Spring + Spring Boot + MyBatis + MongoDB的整合教程
這篇文章主要給大家介紹了關(guān)于Spring + Spring Boot + MyBatis + MongoDB的整合教程,文中通過(guò)圖文以及示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起看看吧。2017-12-12

