0.為什么需要編碼,解碼, 無(wú)論是圖片,文檔,聲音,在網(wǎng)絡(luò)IO,磁盤io中都是以字節(jié)流的方式存在及傳遞的,但是我們拿到字節(jié)流怎么解析呢?這句話就涉及了編碼,解" />

国产成人精品无码青草_亚洲国产美女精品久久久久∴_欧美人与鲁交大毛片免费_国产果冻豆传媒麻婆精东

15158846557 在線咨詢 在線咨詢
15158846557 在線咨詢
所在位置: 首頁(yè) > 營(yíng)銷資訊 > 網(wǎng)站運(yùn)營(yíng) > Javaweb亂碼解決

Javaweb亂碼解決

時(shí)間:2023-10-06 21:12:01 | 來(lái)源:網(wǎng)站運(yùn)營(yíng)

時(shí)間:2023-10-06 21:12:01 來(lái)源:網(wǎng)站運(yùn)營(yíng)

Javaweb亂碼解決:javaweb中涉及的編碼問(wèn)題比較多,慢慢總結(jié)一下

0.為什么需要編碼,解碼,

無(wú)論是圖片,文檔,聲音,在網(wǎng)絡(luò)IO,磁盤io中都是以字節(jié)流的方式存在及傳遞的,但是我們拿到字節(jié)流怎么解析呢?這句話就涉及了編碼,解碼兩個(gè)過(guò)程,從字符數(shù)據(jù)轉(zhuǎn)化為字節(jié)數(shù)據(jù)就是編碼,從字節(jié)數(shù)據(jù)轉(zhuǎn)化為字符數(shù)據(jù)是解碼,可能有人疑問(wèn),一個(gè)字符不是一個(gè)字節(jié),兩個(gè)字節(jié)嗎?一堆字符不就是一堆字節(jié)嗎,需要轉(zhuǎn)什么?好,剛才所說(shuō) 的以及涉及到編碼了,有的編碼是一個(gè)字節(jié)一個(gè)字符,就像ASCII碼,但是漢字以及其他語(yǔ)言文字太多,很明顯一個(gè)字節(jié)不能表示所有字符,所以才會(huì)引申出如 此多的編碼,現(xiàn)在主要討論ISO-8859-1,utf-8,gbk ,其中iso-8859編碼是無(wú)法對(duì)中文進(jìn)行編碼的


String ISO = "ISO-8859-1";
String UTF = "UTF-8";
String GBK = "GBK";
String string = "很開心分享經(jīng)驗(yàn)";
byte[] bytes = string.getBytes(ISO);

System.out.println("結(jié)果:"+new String(bytes,ISO));
for(byte b:bytes){
System.out.print(b+" ");
}


ISO只要遇到不認(rèn)識(shí)的字符,都會(huì)將其用63表示,顯示出來(lái),也就是 ? 所有的都變成了問(wèn)號(hào) 這里可以看到六個(gè)中文對(duì)應(yīng)6個(gè) ?

順便說(shuō)一下,英文不會(huì)涉及iso,utf-8,gbk,編碼問(wèn)題,因?yàn)橛⑽目梢杂肁SCII碼表示,這幾種編碼對(duì)ASCII碼兼容

String string = "很開心分享經(jīng)驗(yàn) happy the world";


32代表 空格

utf-8編解碼

String ISO = "ISO-8859-1";
String UTF = "UTF-8";
String GBK = "GBK";
String string = "很開心分享經(jīng)驗(yàn)";
byte[] bytes = string.getBytes(UTF);

System.out.println("結(jié)果:"+new String(bytes,UTF));
for(byte b:bytes){
System.out.print(b+" ");
}


可以看到 編碼沒(méi)有問(wèn)題,而且每個(gè)漢字占用三個(gè)字節(jié) ,一共21個(gè)(UTF-16一共占用16個(gè))

GBK編碼

代碼省略了


編解碼還是沒(méi)有問(wèn)題, 但是每個(gè)漢字占用 兩個(gè)字節(jié),一共14個(gè)

我們知道了,只要遇到一大堆??????這樣的亂碼,一般可以確認(rèn),有一個(gè)地方用到了iso的編碼,這是中文不能使用的編碼除此之外,我們還會(huì)看到很多種其他的亂碼例如:

1. 寰堝紑蹇?jī)鍒嗕韩缁忛?

2. 2. ??????????

3. oü?aD?·??í??é

4. ?????€???????o????éa

這四種亂碼,好像都不一樣哦,各有各的風(fēng)采,然而并看不懂。

1. 寰堝紑蹇?jī)鍒嗕韩缁忛? UTF-GBK

2. ?????????? GBK-UTF

3. oü?aD?·??í??é GBK-ISO

4. ?????€???????o????éa UTF-ISO

(5 裥?菥袆???? utf-8 ---utf-16



這四種分別對(duì)應(yīng),不同的編碼-解碼 例如第一個(gè) 很開心分享經(jīng)驗(yàn) 用utf-8編碼后,gbk解碼后 就變成了這坨 寰堝紑蹇?jī)鍒嗕韩缁忛? ,剩下三坨不多說(shuō)

現(xiàn)在總結(jié)下(一次編解碼):

1. 只要是iso對(duì)中文字符編碼就一定是一大堆?????,為什么呢,因?yàn)槲覀冋f(shuō)了iso把不認(rèn)識(shí)的都轉(zhuǎn)成63 63是什么在ASCII碼中是? 而這些編碼基本都兼容ASCII,所以

只要是一大坨???????就一定有一個(gè)地方采用的是iso編碼,而后面還會(huì)說(shuō)道iso編碼是很多地方的默認(rèn)編碼(默認(rèn)的為什么不是utf-8,,郁悶!!!)

2 當(dāng)我們看到亂碼之后,不要慌,第一步先不要想在哪里出現(xiàn)編碼問(wèn)題,先考慮 可能是哪一種編解碼 錯(cuò)誤。而這種錯(cuò)誤是有章可循的。上邊內(nèi)四個(gè)基本差不多,(如果還有發(fā)現(xiàn)別的,我會(huì)再加上)

那么如果出現(xiàn)其他好多次編解碼呢? 那這個(gè)問(wèn)題就復(fù)雜的多了

1 .String ISO = "ISO-8859-1";
String UTF = "UTF-8";
String GBK = "GBK";
String string = "很開心分享經(jīng)驗(yàn)";
byte[] bytes = string.getBytes(UTF);
String string2 = new String(bytes,ISO);
byte[] bytes2 = string2.getBytes(ISO);
String string3 = new String(bytes2,UTF);

System.out.println("結(jié)果:"+string3);


UTF編碼,iso解碼,ios解碼,utf-8解碼 中間經(jīng)歷了曲折,但是,最終由變成了 中文,好艱難

但是我們應(yīng)該為此慶幸嗎? 我覺(jué)得不能,你最好也這么覺(jué)得,所有的編解碼,要統(tǒng)一

統(tǒng)一之前,我們也應(yīng)該明白,我們總會(huì)有疏漏的地方,萬(wàn)一一不留神呢,,所以再看看其他的混合編解碼

2.String string = "很開心分享經(jīng)驗(yàn)";
byte[] bytes = string.getBytes(UTF);
String string2 = new String(bytes,ISO);
byte[] bytes2 = string2.getBytes(UTF);
String string3 = new String(bytes2,ISO);

System.out.println("結(jié)果:"+string3);

這一次我們 進(jìn)行了兩次 utf編碼,ios解碼, 但是結(jié)果什么樣子呢


翻翻前面的,基本差不多,只不過(guò)小寫變大寫,最重要的是長(zhǎng)度增加了一倍

3.

byte[] bytes = string.getBytes(UTF);
String string2 = new String(bytes,GBK);
byte[] bytes2 = string2.getBytes(UTF);
String string3 = new String(bytes2,GBK);

System.out.println("結(jié)果:"+bytes2.length+string3);

這一次我們進(jìn)行了 一個(gè)utf-8,編碼,gbk解碼,utf-8編碼,gbk解碼,依然是亂碼,長(zhǎng)度又增加了 二分之一(utf-8表示一個(gè)中文三個(gè)字節(jié),gbk是兩個(gè)字節(jié))


4.byte[] bytes = string.getBytes(UTF);
String string2 = new String(bytes,GBK);
byte[] bytes2 = string2.getBytes(GBK);
String string3 = new String(bytes2,UTF);

System.out.println("結(jié)果:"+string3);

這次是utf-8編碼,gbk解碼,gbk編碼,utf-8解碼。最終的結(jié)果好玩


一部分中文被正確顯示,另外一些漢字慘遭拋棄。。。

5.byte[] bytes = string.getBytes(GBK);
String string2 = new String(bytes,UTF);
byte[] bytes2 = string2.getBytes(UTF);
String string3 = new String(bytes2,GBK);

System.out.println("結(jié)果:"+string3);

這一次是GBK編碼,utf-8解碼,utf-8編碼,gbk解碼


6、

byte[] bytes = string.getBytes(GBK);
String string2 = new String(bytes,UTF);
byte[] bytes2 = string2.getBytes(GBK);
String string3 = new String(bytes2,GBK);

System.out.println("結(jié)果:"+string3);

這次我們gbk編碼,utf-8解碼,gbk編碼,gbk解碼,

得到的結(jié)果感人::


一大坨 ??? 剛才我說(shuō)的都是一大坨 ???編碼肯定是iso,但是得有前提,長(zhǎng)度相同

到這里,不能在總結(jié)了,意思很明確,經(jīng)過(guò)一次兩次編碼,結(jié)果可能已經(jīng)面目全非,但是仔細(xì)分析,每一種情況的具體結(jié)果還是不同的,對(duì)于我們很多人來(lái)說(shuō),要想記住這些所有的亂碼情況,是不可能的,但是如果我們真的遇到了一些多次編解碼,多種編碼方式,出現(xiàn)的亂碼時(shí),有過(guò)這方面的試驗(yàn)經(jīng)驗(yàn),或許可以解決的更快一些。

下面從請(qǐng)求處理的流程,以及具體流程的某個(gè)階段可能出現(xiàn)的亂碼問(wèn)題

1.http請(qǐng)求的編碼

提交表單(一般設(shè)置為method = POST ,一下說(shuō)的表單默認(rèn)是post),或者在地址欄直接輸入url地址(get請(qǐng)求)

首先先說(shuō)get方式,也就是輸入地址欄 的url

1.http://localhost:8080/TestCharSet/test?charset=中文

紅色部分為pathinfo,也就是路徑部分,綠色部分是QueryInfo部分,也就是查詢字符串,在后臺(tái)我們一般這樣獲取

String charset = request.getParameter(“charset”);

以上我在地址欄中輸入的,當(dāng)我從 地址欄復(fù)制到word中時(shí),他轉(zhuǎn)成了這個(gè)

http://localhost:8080/TestCharSet/test?charset=%E4%B8%AD%E6%96%87

后面的%加上數(shù)字字母都是URL編碼 加上%是因?yàn)?6進(jìn)制表示,所以在前面加個(gè)%

URL編碼的過(guò)程很簡(jiǎn)單,如下:

1. 將待編碼字符原先的存儲(chǔ)編碼看成一個(gè)16進(jìn)制流【將原2進(jìn)制流按 字節(jié)拆分,每個(gè)字節(jié)都用2位16進(jìn)制數(shù)表示】;

2. 在每?jī)晌?6進(jìn)制數(shù)(即一個(gè)完整的字節(jié))前加一個(gè)%,得到最終編碼結(jié)果;

對(duì)于漢字來(lái)說(shuō),首先要看其本身存儲(chǔ)時(shí)所使用的編碼是UTF-8還是GB2312。同樣的漢字,存儲(chǔ)編碼不同,經(jīng)URL編碼后的結(jié)果自然也不同。例如“川”,使用UTF-8編碼存儲(chǔ)時(shí)為e5b79d ,經(jīng)URL編碼后則為 %e5%b7%9d ;使用GB2312編碼存儲(chǔ)時(shí)為 b4a8 ,經(jīng)URL編碼后則為 %b4%a8 。


解碼的時(shí)候也很簡(jiǎn)單,將編碼里的%號(hào)去掉,得到一個(gè)16進(jìn)制流,這個(gè)16進(jìn)制流轉(zhuǎn)回2進(jìn)制流,得到的就是原字符的存儲(chǔ)編碼。剩下的一個(gè)重要問(wèn)題是怎么理解這個(gè)還原出來(lái)的存儲(chǔ)編碼(即原字符使用的存儲(chǔ)編碼方式)?分三種情況:

· 對(duì)于HTTP請(qǐng)求正文中的URL編碼,我們可以查看請(qǐng)求頭部中 Charset 頭域的值,它指定了請(qǐng)求報(bào)文所使用的字符集(即存儲(chǔ)編碼方式)。如圖1,因?yàn)?Charset 的值為UTF-8,所以我們對(duì)解碼后的結(jié)果就應(yīng)當(dāng)按UTF-8編碼理解了;

· 因?yàn)槭褂肬TF-8編碼時(shí),一個(gè)漢字的本身存儲(chǔ)占三個(gè)字節(jié);而使用GB2312編碼,一個(gè)漢字的本身存儲(chǔ)占兩個(gè)字節(jié)。因此如果我們能確定被編碼的是純漢字流的話,我們可以根據(jù)解碼后的結(jié)果占用的字節(jié)數(shù)是3或者2的倍數(shù)來(lái)大致推斷其存儲(chǔ)編碼方式;

· 上述方法都不行的話,就只能在譯碼的時(shí)候都試一下了

具體的url編碼請(qǐng)查看鏈接 HTTP協(xié)議分析中幾種常見的中文編碼及其解碼 - 推酷 這里寫的很好

前面說(shuō)到了 瀏覽器把一個(gè)具體編碼的字符序列按照url編碼 為16進(jìn)制,同樣服務(wù)器端,按照url解碼,將url進(jìn)行解碼 得到了具體的字符序列,那么服務(wù)器拿到了這個(gè)字符序列怎么辦呢,就可以讀取了嗎,不能,我們還需要知道這個(gè)字符序列是什么編碼方式,否則我們根本正常讀取不了(否則就會(huì)亂碼)

2)post方式的編碼

例如表單POST請(qǐng)求,會(huì)將提交的參數(shù)放到請(qǐng)求主體部分


例如name就被放在主體部分,同樣,它也是瀏覽器通過(guò)url編碼(16進(jìn)制編碼)把數(shù)據(jù)傳送到服務(wù)器端,具體的服務(wù)器拿到這個(gè)16進(jìn)制解碼的結(jié)果,也就是這個(gè)字符序列,如何處理,是按照什么方式解析呢,同樣是 制定的charset值,但是一般表單提交后,服務(wù)器端需要我們指定具體的charset進(jìn)行解碼,具體的方式

大家都用過(guò):request.setCharacterEncoding(charset);

3、應(yīng)用服務(wù)器如何解析參數(shù)

我們知道url也就是get請(qǐng)求的路徑部分,需要解碼,解碼的方式是charset指定的方式,但是具體的,可能頭文件中并沒(méi)有指定charset,這時(shí) 應(yīng)用服務(wù)器會(huì)采用默認(rèn)的編碼方式

具體到tomcat中

<Connector URIEncoding="UTF-8" port="8080" protocol="HTTP/1.1"

connectionTimeout="20000"

redirectPort="8443" />

這個(gè)uriencoding屬性可以設(shè)置tomcat的解析的uri的編碼

如果不設(shè)置,默認(rèn)是IOS-8859-1,uri中存在中文字符,即使設(shè)置了request.setCharaeterEncoding(“utf-8”)在request中也解析不出來(lái)的。

如下

protected void doGet(HttpServletRequest req, HttpServletResponse resp)

throws ServletException, IOException {

System.out.println("GET請(qǐng)求處理");

req.setCharacterEncoding("utf-8");//在這里設(shè)置了utf-8

String string = req.getParameter("charset");

if(string!=null){

System.out.println(string);

}else {

System.out.println("沒(méi)有獲取到變量");

}


PrintWriter pw = resp.getWriter();

pw.println(req.getRequestURI()+"請(qǐng)求成功");

pw.close();

}

請(qǐng)求路徑中包含中文

http://localhost:8080/TestCharSet/test?charset=中文


如果設(shè)置了URIEncoding="UTF-8"之后,即使沒(méi)有設(shè)置reqest.setCharaeterEncoding,也可以獲取到參數(shù),但是對(duì)于post請(qǐng)求,無(wú)法獲取到name



當(dāng)設(shè)置了reqest.setCharaeterEncoding(utf-8)后,post請(qǐng)求可以獲取到


我們可以總結(jié)一下,也就是說(shuō)url中的編碼必須統(tǒng)一uriEncoding設(shè)置,可以不設(shè)置reqest.setCharaeterEncoding(utf-8)

但是如果遇到post請(qǐng)求,參數(shù)信息在請(qǐng)求主體中,那我們必須設(shè)置

reqest.setCharaeterEncoding(utf-8)這樣才能保證獲取到正確的參數(shù)信息。

這里多提一點(diǎn),tomcat在對(duì)請(qǐng)求頭文件解析的時(shí)候,默認(rèn)是先不解析請(qǐng)求主體字符串,因?yàn)樽址僮鞣浅:馁M(fèi)性能,tomcat把解析工作延遲到第一次調(diào)用 req.getParameter中進(jìn)行,之后便將所有的參數(shù)全部注入到tomcat中的一個(gè)ParameterMap數(shù)據(jù)結(jié)構(gòu)中,在解析之后,你即使通過(guò)req.setCharaeter也沒(méi)有用了。

(在由byte[]流轉(zhuǎn)化為Java中的String時(shí),需要指定編碼,這個(gè)編碼就是通過(guò)request設(shè)置的)

Tomcat還有一個(gè)參數(shù)useBodyEncodingForURI,這個(gè)參數(shù)是什么意思呢,當(dāng)它為false時(shí),對(duì)url中的編碼采用tomcat默認(rèn)的或者uriEncoding設(shè)置的編碼方式。當(dāng)為true時(shí),get請(qǐng)求中的查詢字符串按照http請(qǐng)求主體中的編碼方式解碼,而請(qǐng)求主體的解碼方式,我們通過(guò)

request.setCharaeterEncoding設(shè)置,而瀏覽器發(fā)送請(qǐng)求主體的編碼方式是發(fā)送post,get請(qǐng)求的本界面的charset進(jìn)行編碼。后面會(huì)具體談到瀏覽器發(fā)送http請(qǐng)求的編碼

不光request,response也需要字符轉(zhuǎn)化,看個(gè)例子

這時(shí)處理get請(qǐng)求的servlet代碼,像屏幕打印輸出請(qǐng)求鏈接,以及“請(qǐng)求成功”

PrintWriter pw = resp.getWriter();

pw.println(req.getRequestURI()+"請(qǐng)求成功");

pw.close();


可以看到請(qǐng)求成功是亂碼,因?yàn)閞esponse將”請(qǐng)求成功“轉(zhuǎn)化為 byte[]流時(shí),默認(rèn)采用的

ISO-8859-1,所以打印的都是????

基于此reponse也要設(shè)置字符編碼,response.setCharaeterEncoding(“utf-8”)


這時(shí)顯示正常了

但是可能有的小伙伴顯示的結(jié)果可能是這個(gè)


還是亂碼,往上面找我們總結(jié)的四種情況,最接近第一種,也就是說(shuō)用utf-8編碼,gbk解碼的情況,事實(shí)也是如此,瀏覽器接收到byte[]流后,按照的是GBK解碼,我用的是火狐瀏覽器,在文字編碼設(shè)置中,將簡(jiǎn)體中文改成Unicode后,即正常顯示。

或者我們采用一種更加優(yōu)雅的方式

pw.println("<meta http-equiv=/"Content-Type/" content=/"text/html;charset=UTF-8/">");

打印meta標(biāo)簽,這樣即使我們手動(dòng)指定文字編碼為gbk(簡(jiǎn)體中文),瀏覽器接收到這個(gè)標(biāo)簽后,會(huì)默認(rèn)按照這個(gè)標(biāo)簽中指定的編碼,進(jìn)行解碼顯示。同樣,meta標(biāo)簽指定的編碼也要和response.setXXX方法設(shè)置的一樣,否則也會(huì)亂碼

再繼續(xù)深入一下,這是通過(guò)response打印輸出,如果直接跳轉(zhuǎn)到j(luò)sp頁(yè)面呢?首先我們先不設(shè)置 response的編碼集也就是默認(rèn)的轉(zhuǎn)到Charser.jsp界面該界面如下

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>

<%

String path = request.getContextPath();

String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";

%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>

<head>

<base href="<%=basePath%>">

<title></title>

<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />

<meta http-equiv="pragma" content="no-cache">

<meta http-equiv="cache-control" content="no-cache">

<meta http-equiv="expires" content="0">

<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">

<meta http-equiv="description" content="This is my page">

<!--

<link rel="stylesheet" type="text/css" href="styles.css">

-->

</head>

<body>

<h1>copyright @ 青銅器工作室 </h1>

</body>

</html>

在這個(gè)界面里,有一段中文,看看這個(gè)界面能不能正常顯示

req.getRequestDispatcher("/Charset.jsp").forward(req, resp);

這個(gè)將請(qǐng)求轉(zhuǎn)發(fā)到Charset.jsp這個(gè)界面


結(jié)果如圖,response在沒(méi)有設(shè)置編碼情況下,還是將jsp中的中文按照iso進(jìn)行編碼,變成了???

有什么方式可以避免這種情況發(fā)生呢

事實(shí)上我們可以看到

即使加上<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>

如果不用response設(shè)置默認(rèn)編碼,同樣會(huì)亂碼 因?yàn)閞esponse設(shè)置的編碼是用來(lái)編碼的,就是講字符轉(zhuǎn)化為字節(jié)數(shù)組,而meta設(shè)置的utf-8是用來(lái)解碼的,也就是說(shuō)告訴,瀏覽器,你應(yīng)該如何顯示

但是我發(fā)現(xiàn),

當(dāng)設(shè)置了response的編碼

設(shè)置了<meta http-equiv="Content-Type" content="text/html;charset=gbk" />

時(shí)客戶端沒(méi)有顯示亂碼,預(yù)測(cè)結(jié)果是客戶端顯示界面時(shí)應(yīng)該按gbk解碼 用utf-8編碼后的字符。

關(guān)鍵在于 charset.jsp這個(gè)界面我是通過(guò)request轉(zhuǎn)發(fā)過(guò)去的,在處理中response設(shè)置的utf-8已經(jīng)指定了http響應(yīng)頭的編碼


req.getRequestDispatcher("/Charset.jsp").forward(req, resp);

事實(shí)上response設(shè)置的utf-8已經(jīng)設(shè)置了jsp編碼的方式,同時(shí)在響應(yīng)頭中指定了charset,這樣jsp設(shè)置的charset,沒(méi)有作用。

那么jsp中的

<meta http-equiv="Content-Type" content="text/html;charset=gbk" />

以及在

<%@ page language="java" contentType="text/html;charset=GBK" import="java.util.*" pageEncoding="UTF-8"%>中設(shè)置的ContentType在哪里其作用呢?pageEncoding又是什么呢?

以下引用

JSP中pageEncoding和charset區(qū)別,中文亂碼解決方案 - 花郎V - 博客園下內(nèi)容

pageEncoding是jsp文件本身的編碼

  contentType的charset是指服務(wù)器發(fā)送給客戶端時(shí)的內(nèi)容編碼

  JSP要經(jīng)過(guò)兩次的“編碼”,第一階段會(huì)用pageEncoding,第二階段會(huì)用utf-8至utf-8,第三階段就是由Tomcat出來(lái)的網(wǎng)頁(yè), 用的是contentType。

  第一階段是jsp編譯成.java,它會(huì)根據(jù)pageEncoding的設(shè)定讀取jsp,結(jié)果是由指定的編碼方案翻譯成統(tǒng)一的UTF-8 JAVA源碼(即.java),如果pageEncoding設(shè)定錯(cuò)了,或沒(méi)有設(shè)定,出來(lái)的就是中文亂碼。

  第二階段是由JAVAC的JAVA源碼至java byteCode的編譯,不論JSP編寫時(shí)候用的是什么編碼方案,經(jīng)過(guò)這個(gè)階段的結(jié)果全部是UTF-8的encoding的java源碼。

  JAVAC用UTF-8的encoding讀取java源碼,編譯成UTF-8 encoding的二進(jìn)制碼(即.class),這是JVM對(duì)常數(shù)字串在二進(jìn)制碼(java encoding)內(nèi)表達(dá)的規(guī)范。

  第三階段是Tomcat(或其的application container)載入和執(zhí)行階段二的來(lái)的JAVA二進(jìn)制碼,輸出的結(jié)果,也就是在客戶端見到的,這時(shí)隱藏在階段一和階段二的參數(shù)contentType就發(fā)揮了功效

  contentType的設(shè)定.

  pageEncoding 和contentType的預(yù)設(shè)都是 ISO8859-1. 而隨便設(shè)定了其中一個(gè), 另一個(gè)就跟著一樣了(TOMCAT4.1.27是如此). 但這不是絕對(duì)的, 這要看各自JSPC的處理方式.而pageEncoding不等于contentType, 更有利亞洲區(qū)的文字 CJKV系JSP網(wǎng)頁(yè)的開發(fā)和展示, (例pageEncoding=GB2312 不等于 contentType=utf-8)。

  jsp文件不像.java,.java在被編譯器讀入的時(shí)候默認(rèn)采用的是操作系統(tǒng)所設(shè)定的locale所對(duì)應(yīng)的編碼,比如中國(guó)大陸就是GBK, 臺(tái)灣就是BIG5或者M(jìn)S950。而一般我們不管是在記事本還是在ue中寫代碼,如果沒(méi)有經(jīng)過(guò)特別轉(zhuǎn)碼的話,寫出來(lái)的都是本地編碼格式的內(nèi)容。所以編譯器 采用的方法剛好可以讓虛擬機(jī)得到正確的資料。

  但是jsp文件不是這樣,它沒(méi)有這個(gè)默認(rèn)轉(zhuǎn)碼過(guò)程,但是指定了pageEncoding就可以實(shí)現(xiàn)正確轉(zhuǎn)碼了。

1、pageEncoding="UTF-8"的作用是設(shè)置JSP編譯成Servlet時(shí)使用的編碼。
眾所周知,JSP在服務(wù) 器上是要先被編譯成Servlet的。pageEncoding="UTF-8"的作用就是告訴JSP編譯器在將JSP文件編譯成Servlet時(shí)使用的 編碼。通常,在JSP內(nèi)部定義的字符串(直接在JSP中定義,而不是從瀏覽器提交的數(shù)據(jù))出現(xiàn)亂碼時(shí),很多都是由于該參數(shù)設(shè)置錯(cuò)誤引起的。例如,你的 JSP文件是以GBK為編碼保存的,而在JSP中卻指定pageEncoding="UTF-8",就會(huì)引起JSP內(nèi)部定義的字符串為亂碼。
另外,該參數(shù)還有一個(gè)功能,就是在JSP中不指定contentType參數(shù),也不使用response.setCharacterEncoding方法時(shí),指定對(duì)服務(wù)器響應(yīng)進(jìn)行重新編碼的編碼。
2、contentType="text/html;charset=UTF-8"的作用是指定對(duì)服務(wù)器響應(yīng)進(jìn)行重新編碼的編碼。
在不使用response.setCharacterEncoding方法時(shí),用該參數(shù)指定對(duì)服務(wù)器響應(yīng)進(jìn)行重新編碼的編碼。

剛才我們就是在設(shè)置response.setCharacterEncoding(utf-8)后,設(shè)置contentType為gbk,但是客戶端依然按照response的設(shè)置,所以response對(duì)contentType的優(yōu)先級(jí)高

3、request.setCharacterEncoding("UTF-8")的作用是設(shè)置對(duì)客戶端請(qǐng)求進(jìn)行重新編碼的編碼。

該方法用來(lái)指定對(duì)瀏覽器發(fā)送來(lái)的數(shù)據(jù)進(jìn)行重新編碼(或者稱為解碼)時(shí),使用的編碼。
4、response.setCharacterEncoding("UTF-8")的作用是指定對(duì)服務(wù)器響應(yīng)進(jìn)行重新編碼的編碼。
服務(wù)器在將數(shù)據(jù)發(fā)送到瀏覽器前,對(duì)數(shù)據(jù)進(jìn)行重新編碼時(shí),使用的就是該編碼。

當(dāng)我們直接通過(guò)訪問(wèn)jsp的方式,轉(zhuǎn)到j(luò)sp界面時(shí),發(fā)現(xiàn) 在page標(biāo)簽中contentType起作用了,我們?cè)O(shè)置的是gbk


然后我們發(fā)現(xiàn)通過(guò)meta設(shè)置的編碼為utf-8時(shí),也并沒(méi)有出現(xiàn)亂碼,可見,瀏覽器顯示時(shí),并沒(méi)有按照meta標(biāo)簽中指定的編碼,而是優(yōu)先使用page標(biāo)簽中的contentType中設(shè)置的值。這時(shí)即使設(shè)置了字符過(guò)濾器,字符過(guò)濾器中response中的設(shè)置也不會(huì)啟用,也即是說(shuō),

當(dāng)直接訪問(wèn)jsp時(shí),只會(huì)看

<%@ page language="java" contentType="text/html;charset=gbk" import="java.util.*" pageEncoding="UTF-8"%>

的設(shè)置,(過(guò)濾器什么的都不會(huì)起作用)

當(dāng)采用servlet中請(qǐng)求轉(zhuǎn)發(fā)方式時(shí),過(guò)濾器或servlet中的response的設(shè)置會(huì)起作用

當(dāng)在html頁(yè)面中,使用meta標(biāo)簽會(huì)起作用,當(dāng)沒(méi)有meta標(biāo)簽中會(huì)默認(rèn)按照utf-8編碼(并不推薦默認(rèn))

下面我們?cè)诜治鲆幌?,已開始討論的瀏覽器端的處理

默認(rèn)的情況下,如果jsp中出現(xiàn)這個(gè)超級(jí)鏈接應(yīng)該如何處理呢。

中文">點(diǎn)擊這里</a>

瀏覽器解析這個(gè)本界面時(shí)按照response的設(shè)置,或者page標(biāo)簽中的contentType設(shè)置(也就是響應(yīng)頭中的charset)進(jìn)行解析,所以這個(gè)鏈接中的中文自然是按照響應(yīng)中的設(shè)置進(jìn)行編碼,而這是一個(gè)get請(qǐng)求,我們知道get請(qǐng)求瀏覽器需要將url進(jìn)行url編碼(16進(jìn)制編碼),我們需要知道url本身的編碼是utf-8,還是gbk,這樣服務(wù)器獲取到字符序列才能進(jìn)行重新編碼,

當(dāng)我們的url是通過(guò)超級(jí)鏈接跳轉(zhuǎn)的,編碼方式按照本界面response的字符編碼設(shè)定。

當(dāng)我們直接通過(guò)地址欄輸入中文url,這時(shí)url會(huì)按照瀏覽器自己默認(rèn)的編碼方式編碼,火狐是utf-8,360也是utf-8,至于其他的瀏覽器是不是utf-8不確定。

那么post方式,瀏覽器是采用什么編碼的呢?

和上面所說(shuō)的一樣,瀏覽器會(huì)解析response設(shè)置的響應(yīng)頭的charset,然后確定post請(qǐng)求的主體采用什么編碼

如果響應(yīng)頭中charset是utf-8時(shí),那么下次它發(fā)送http請(qǐng)求,主體部分會(huì)是utf-8.

引用這個(gè)鏈接中的一段話JSP中pageEncoding和charset區(qū)別,中文亂碼解決方案 - 花郎V - 博客園

response.setCharacterEncoding("UTF- 8")的作用是指定對(duì)服務(wù)器響應(yīng)進(jìn)行重新編碼的編碼。同時(shí),瀏覽器也是根據(jù)這個(gè)參數(shù)來(lái)對(duì)其接收到的數(shù)據(jù)進(jìn)行重新編碼(或者稱為解碼)。所以在無(wú)論你在 JSP中設(shè)置response.setCharacterEncoding("UTF-8")或者 response.setCharacterEncoding("GBK"),瀏覽器均能正確顯示中文(前提是你發(fā)送到瀏覽器的數(shù)據(jù)編碼是正確的,比如正 確設(shè)置了pageEncoding參數(shù)等)。讀者可以做個(gè)實(shí)驗(yàn),在JSP中設(shè)置 response.setCharacterEncoding("UTF- 8"),在IE中顯示該頁(yè)面時(shí),在IE的菜單中選擇"查看(V)"à"編碼(D)"中可以查看到是" Unicode(UTF-8)",而在在JSP中設(shè)置response.setCharacterEncoding("GBK"),在IE中顯示該頁(yè)面 時(shí),在IE的菜單中選擇"查看(V)"à"編碼(D)"中可以查看到是"簡(jiǎn)體中文(GB2312)"。
瀏覽器在發(fā)送數(shù)據(jù)時(shí),對(duì)URL和參數(shù)會(huì) 進(jìn)行URL編碼,對(duì)參數(shù)中的中文,瀏覽器也是使response.setCharacterEncoding參數(shù)來(lái)進(jìn)行URL編碼的。以百度和 GOOGLE為例,如果你在百度中搜索"漢字",百度會(huì)將其編碼為"%BA%BA%D7%D6"。而在GOOGLE中搜索"漢字",GOOGLE會(huì)將其編 碼為"%E6%B1%89%E5%AD%97",這是因?yàn)榘俣鹊膔esponse.setCharacterEncoding參數(shù)為GBK,而 GOOGLE的的response.setCharacterEncoding參數(shù)為UTF-8。
瀏覽器在接收服務(wù)器數(shù)據(jù)和發(fā)送數(shù)據(jù)到服務(wù)器 時(shí)所使用的編碼是相同的,默認(rèn)情況下均為JSP頁(yè)面的response.setCharacterEncoding參數(shù)(或者contentType和 pageEncoding參 數(shù)),我們稱其為瀏覽器編碼。當(dāng)然,在IE中可以修改瀏覽器編碼(在IE的菜單中選擇"查看(V)"à"編碼(D)"中修 改),但通常情況下,修改該參數(shù)會(huì)使原本正確的頁(yè)面中出現(xiàn)亂碼。一個(gè)有趣的例子是,在IE中瀏覽GOOGLE的主頁(yè)時(shí),將瀏覽器編碼修改為"簡(jiǎn)體中文 (GB2312)",此時(shí),頁(yè)面上的中文會(huì)變成亂碼,不理它,在文本框中輸入"漢字",提交,GOOGLE會(huì)將其編碼為"%BA%BA%D7%D6",可 見,瀏覽器在對(duì)中文進(jìn)行URL編碼時(shí),使用的就是瀏覽器編碼。

所以至此整個(gè)瀏覽器發(fā)送http請(qǐng)求,服務(wù)器獲取數(shù)據(jù),發(fā)送數(shù)據(jù),瀏覽器接受數(shù)據(jù)中涉及的編碼基本上都已經(jīng)說(shuō)了,那項(xiàng)目中還有哪些編碼問(wèn)題呢,或者哪些編碼問(wèn)題隱藏比較深呢?

1.數(shù)據(jù)庫(kù)的編碼,數(shù)據(jù)庫(kù)一般不會(huì)出現(xiàn)編碼問(wèn)題前提是我們創(chuàng)建數(shù)據(jù)庫(kù)時(shí)需要指定編碼

CREATE TABLE `t_image` (

`id` int(11) NOT NULL AUTO_INCREMENT,

`fkey` varchar(50) DEFAULT NULL,

`src` varchar(500) NOT NULL,

`des1` varchar(500) DEFAULT NULL,

`des2` varchar(500) DEFAULT NULL,

`des3` varchar(500) DEFAULT NULL,

`des4` varchar(500) DEFAULT NULL,

PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

同時(shí)連接數(shù)據(jù)庫(kù)時(shí)也要制定編碼

jdbc:mysql://localhost:3306/psych?Unicode=true&characterEncoding=utf-8

這種情況下基本上不會(huì)出現(xiàn)數(shù)據(jù)庫(kù)編碼問(wèn)題,在這種情況下,如果出現(xiàn)亂碼問(wèn)題,最后考慮數(shù)據(jù)庫(kù),因?yàn)橛锌赡艽孢M(jìn)去的時(shí)候就是亂碼

什么意思呢,例如你將一個(gè) string轉(zhuǎn)化為byte[]使用gbk編碼,然后存進(jìn)數(shù)據(jù)庫(kù),這時(shí)你如果讀取后,將byte[]按照utf-8進(jìn)行解碼,本身就是錯(cuò)的,后面我會(huì)說(shuō)到我的一個(gè)經(jīng)歷,這個(gè)經(jīng)歷很久了,但bug隱藏的很深,以至于 寫博客之前,我才恍然大悟?yàn)槭裁闯霈F(xiàn)問(wèn)題。

2.tomcat的默認(rèn)編碼問(wèn)題。

為什么要說(shuō)tomcat的默認(rèn)編碼?剛才不已經(jīng)說(shuō)了嘛,通過(guò)uriEncoding設(shè)置。此處說(shuō)的默認(rèn)編碼設(shè)置是tomcat本身作為一個(gè)javaweb應(yīng)用服務(wù)器,它本身就是一個(gè)進(jìn)程,而一個(gè)進(jìn)程實(shí)例就是一個(gè)虛擬機(jī),我們?cè)趈ava里很少說(shuō)進(jìn)程,取而代之的是虛擬機(jī),實(shí)際上,tomcat也是從main函數(shù)開始,中間有線程監(jiān)聽請(qǐng)求,監(jiān)聽到交給處理器線程處理,處理器針對(duì)http請(qǐng)求流進(jìn)行解析,將其封裝為request,response,其中response中封裝了outputStream,也是即將發(fā)送給客戶端的輸出流,然后映射器(tomcat5以后沒(méi)了,但道理一樣)找到我們定義的servlet,將其加載,包裝成tomcat中的類進(jìn)行業(yè)務(wù)處理。以上我們討論的都是每層每個(gè)階段之間交換數(shù)據(jù)出現(xiàn)的編碼問(wèn)題。

而這里談到的默認(rèn)編碼是指tomcat進(jìn)程(虛擬機(jī))本身采用的字符編碼,可以通過(guò)

System.out.println(java.nio.charset.Charset.defaultCharset());

查看在windows下默認(rèn)是gbk,linux下默認(rèn)是utf-8

一般情況下,我們即使在windows下面,一個(gè)java進(jìn)程采用的編碼是utf-8

但是tomcat進(jìn)程不同,它是gbk。

那這個(gè)默認(rèn)字符編碼作用是什么呢?

String string = "很開心分享經(jīng)驗(yàn)";

byte[] bytes = string.getBytes(UTF);

String string2 = new String(bytes);

這段代碼執(zhí)行結(jié)果是什么呢那要看具體的環(huán)境了在windows下的tomcat,絕對(duì)是亂碼

在linux下的tomcat,就不亂,如果是默認(rèn)的進(jìn)程(你自己寫個(gè)main,然后這三代碼,不加任何虛擬機(jī)初始參數(shù))那不是亂碼。

一開始我并沒(méi)有發(fā)現(xiàn)windows下的tomcat默認(rèn)編碼是gbk,我在測(cè)試數(shù)據(jù)庫(kù)時(shí),采用的是本地測(cè)試,把字符串轉(zhuǎn)化為byte[]時(shí),采用的是默認(rèn)的,這時(shí)默認(rèn)的應(yīng)該是utf,然后tomcat在顯示時(shí)依然采用默認(rèn)的進(jìn)行解碼(gbk)這時(shí),就出現(xiàn)了亂碼。然而我當(dāng)時(shí)百思不得其解,誤以為是數(shù)據(jù)庫(kù)的錯(cuò)。浪費(fèi)了不少時(shí)間依然沒(méi)有解決。還在懷疑是不是web請(qǐng)求處理中出現(xiàn)亂碼。這就是知識(shí)面不全,存在疏漏之處的后果。有時(shí)候我們很有必要建立全面深刻的知識(shí)體系,不能一知半解,即使是小知識(shí)點(diǎn)也要好好解決掉,否則出現(xiàn)了bug這些不足都會(huì)以時(shí)間成本來(lái)報(bào)復(fù)你。

那默認(rèn)編碼如何解決呢,答案是這樣的,我找了很多初始參數(shù)但是都不對(duì),于是想個(gè)邪招

Field field = Charset.class.getDeclaredField("defaultCharset");

field.setAccessible(true);

try {

field.set(Charset.class,Charset.forName("GBK"));

} catch (IllegalArgumentException e) {

//

e.printStackTrace();

} catch (IllegalAccessException e) {

//

e.printStackTrace();

}

通過(guò)反射獲取靜態(tài)類的私有字段,然后修改,這時(shí)

String string = "很開心分享經(jīng)驗(yàn)";

byte[] bytes = string.getBytes(UTF);

String string2 = new String(bytes);

我們通過(guò)getBytes,以及new String().默認(rèn)的都是我們?cè)O(shè)置的gbk,或者utf-8了。



排查實(shí)踐

曾經(jīng)做過(guò)的項(xiàng)目中,出現(xiàn)這樣一種亂碼情景:

用戶的評(píng)論提交之后,在后來(lái)的查詢中出現(xiàn)亂碼,報(bào)表中其他列正常顯示,評(píng)論一列也不是所有的都是亂碼。只有個(gè)別列是亂碼。

經(jīng)過(guò)后來(lái)的測(cè)試,我們發(fā)現(xiàn) 1.用戶評(píng)論提交后,在控制臺(tái)打印評(píng)論字段是正常中文,2.查詢提交評(píng)論的Http請(qǐng)求發(fā)送類型為Post,3.在自己電腦上測(cè)試基本沒(méi)有出現(xiàn)亂碼情況4.我問(wèn)相關(guān)模塊作者,評(píng)論在數(shù)據(jù)庫(kù)是怎樣存儲(chǔ)的,有沒(méi)有String與byte[]的轉(zhuǎn)換。答案是沒(méi)有。

5.數(shù)據(jù)庫(kù)連接URL指定了UTF8編碼

我們現(xiàn)在可以排查一下

(1).提交評(píng)論是Post,類型,排除了get請(qǐng)求亂碼,并且控制臺(tái)打印輸出是正常中文,可以判斷數(shù)據(jù)輸入過(guò)程不存在亂碼。

(輸入亂碼包括1.Tomcat配置中Connector下的URIEncoding,useURIBodyEncoding,以及2.AJP中的相關(guān)URIEncoding,useURIBodyEncoding,AJP是Tomcat和Apache傳輸消息的協(xié)議,比http效率高一點(diǎn)3.字符過(guò)濾器)

(2)代碼中沒(méi)有出現(xiàn)byte[]和String之間的轉(zhuǎn)換,數(shù)據(jù)庫(kù)連接也指定了編碼,但是不清晰數(shù)據(jù)庫(kù)是如何存儲(chǔ)的。

現(xiàn)在大家應(yīng)該也能猜到,由于 自己電腦上測(cè)試沒(méi)有出現(xiàn)亂碼?那么亂碼是如何出現(xiàn)的呢?

我們可以猜測(cè),是別人平臺(tái)上上傳的數(shù)據(jù)在自己的平臺(tái)上就出現(xiàn)了亂碼??!

具體情景可能是:A在windows平臺(tái)的tomcat開發(fā),提交了一個(gè)評(píng)論保存在數(shù)據(jù)庫(kù),B在linux平臺(tái)下的Tomcat進(jìn)行開發(fā),讀取了A保存在數(shù)據(jù)庫(kù)中的數(shù)據(jù)。。。然后就出現(xiàn)亂碼.

基于這個(gè)猜測(cè):我通過(guò)上面的反射方法分別更改Tomat編碼集為gbk.utf-8,交替提交評(píng)論,查看評(píng)論。果然出現(xiàn)這個(gè)亂碼!??!但是我們明明沒(méi)有String和byte[]之間的轉(zhuǎn)化,更談不上使用默認(rèn)編碼集,從而因?yàn)門omcat在不同操作系統(tǒng)平臺(tái)下的默認(rèn)編碼不同,導(dǎo)致發(fā)生上述猜測(cè)中描述的錯(cuò)誤。那問(wèn)題到底出在哪?

問(wèn)題就在于我們做的系統(tǒng)時(shí)工作流系統(tǒng),使用了開源框架activiti工作流引擎,這是針對(duì)具體工作流業(yè)務(wù)場(chǎng)景的框架,框架負(fù)責(zé)創(chuàng)建數(shù)據(jù)庫(kù),提供數(shù)據(jù)訪問(wèn)修改操作,客戶端不需要關(guān)心數(shù)據(jù)庫(kù)是如何存儲(chǔ)的,但是后來(lái)通過(guò)檢查它的數(shù)據(jù)庫(kù)還是找到了,評(píng)論在數(shù)據(jù)庫(kù)中分別以varchar和blob存儲(chǔ)(不知道為什么存儲(chǔ)兩份),其中blob中保存數(shù)據(jù)編碼就是混亂的,有的是utf-8,有的是gbk,錯(cuò)誤很明顯發(fā)生在activiti框架內(nèi)部,其內(nèi)部在String和byte間的轉(zhuǎn)化肯定是采用默認(rèn)編碼集。通過(guò)上述反射代碼在啟動(dòng)時(shí)指定編碼集即可解決此問(wèn)題。

關(guān)鍵詞:解決

74
73
25
news

版權(quán)所有? 億企邦 1997-2025 保留一切法律許可權(quán)利。

為了最佳展示效果,本站不支持IE9及以下版本的瀏覽器,建議您使用谷歌Chrome瀏覽器。 點(diǎn)擊下載Chrome瀏覽器
關(guān)閉