1110xxxx(E0-EF) 10yyyyyy 10zzzzzz,我們從低位開始補(bǔ)起,不夠的用0補(bǔ)齊。
1110
0100 10
111000 10
101101 ,換成16進(jìn)制為E4 B8 AD。
好了我們用java代碼來(lái)驗(yàn)證下,是否正確。
public static void main(String[] args) {
String ha = "中";
byte b[] = null;
try {
b = ha.getBytes("utf-8");
} catch (Exception e) {
System.exit(-1);
}
for (int i = 0; i < b.length; i++) {
System.out.print(Integer.toHexString(b[i]).substring(6) + " ");
}
}
輸出果然是:e4 b8 ad。
utf8 wiki中有下描述:
- 對(duì)于UTF-8編碼中的任意字節(jié)B,如果B的第一位為0,則B為ASCII碼,并且B獨(dú)立的表示一個(gè)字符;
- 如果B的第一位為1,第二位為0,則B為一個(gè)非ASCII字符(該字符由多個(gè)字節(jié)表示)中的一個(gè)字節(jié),并且不為字符的第一個(gè)字節(jié)編碼;
- 如果B的前兩位為1,第三位為0,則B為一個(gè)非ASCII字符(該字符由多個(gè)字節(jié)表示)中的第一個(gè)字節(jié),并且該字符由兩個(gè)字節(jié)表示;
- 如果B的前三位為1,第四位為0,則B為一個(gè)非ASCII字符(該字符由多個(gè)字節(jié)表示)中的第一個(gè)字節(jié),并且該字符由三個(gè)字節(jié)表示;
- 如果B的前四位為1,第五位為0,則B為一個(gè)非ASCII字符(該字符由多個(gè)字節(jié)表示)中的第一個(gè)字節(jié),并且該字符由四個(gè)字節(jié)表示;
因此,對(duì)UTF-8編碼中的任意字節(jié),根據(jù)第一位,可判斷是否為ASCII字符;根據(jù)前二位,可判斷該字節(jié)是否為一個(gè)字符編碼的第一個(gè)字節(jié); 根據(jù)前四位(如果前兩位均為1),可確定該字節(jié)為字符編碼的第一個(gè)字節(jié),并且可判斷對(duì)應(yīng)的字符由幾個(gè)字節(jié)表示;根據(jù)前五位(如果前四位為1),可判斷編碼 是否有錯(cuò)誤或數(shù)據(jù)傳輸過(guò)程中是否有錯(cuò)誤。
反過(guò)來(lái),我們還是拿剛才的”中“為例,1110
0100 10
111000 10
101101 ,第一個(gè)字節(jié)開始為110,則讀第二個(gè)字節(jié)為10,第三個(gè)字節(jié)為10,則認(rèn)為是utf8字符。
于是就有了一個(gè)那個(gè)經(jīng)典的“聯(lián)通"干不過(guò)”移動(dòng)“的經(jīng)典段子。
我們?cè)趚p下,隨便建立一個(gè)文件,輸入"聯(lián)通",保存,這時(shí)你在打開是,發(fā)現(xiàn)”聯(lián)通"2個(gè)字符不見了。奇怪嗎??????
我們知道默認(rèn)保存的編碼是ANSI,實(shí)際也是類GBK的編碼。
對(duì)應(yīng)16進(jìn)制為c1 aa cd a8, 轉(zhuǎn)化成二進(jìn)制為11000001 10101010 11001101 10101000 ,我們來(lái)看,110xxxxx,10xxxxxx 正好符合utf8的形式。
這時(shí)候文件編寫器以為你的文件是utf8的文件,然后默認(rèn)已utf8的形式給你打開展示。于是就出現(xiàn)亂碼了。如果你在”聯(lián)通“后面隨便加幾個(gè)字符。就不出出現(xiàn)靈異事件了。
那么我們繼續(xù)討論 GBK和Unicode是什么關(guān)系呢?
實(shí)際上GBK我們可以看做是字符集,他也有自己一一對(duì)應(yīng)的碼表。google一下,很容易查到。這里有個(gè)Unicode和GBk對(duì)應(yīng)的表
Unicode-GBk。
在java中,
"我愛(ài)你莎莎".getBytes("gbk");
進(jìn)行轉(zhuǎn)化,其實(shí)就是類似查一個(gè)Unicode和GBk對(duì)應(yīng)表進(jìn)行轉(zhuǎn)化的。大家看一下Charset這個(gè)抽象類的那些子類就明白了。
通過(guò)上面的描述GBk和UTF8關(guān)系也就很明朗了,完全可以通過(guò)Unicode進(jìn)行中轉(zhuǎn)。
同事在詢問(wèn)編碼的問(wèn)題時(shí),一開始對(duì)類似如下代碼,相互轉(zhuǎn)變不太理解。
byte b1[] = null;
b1 = "我愛(ài)你莎莎".getBytes("gbk");
System.out.println(new String(b1,"gbk"));
byte b2[] = null;
b2 = "我愛(ài)你莎莎".getBytes("utf8");
System.out.println(new String(b2,"utf8"));
System.out.println(new String (new String (b2,"gbk").getBytes("gbk"),"utf8"));
其實(shí)我們可以把getBytes("gbk"),這個(gè)函數(shù)當(dāng)做將unicode用gkb加密的過(guò)程,而new String(”xxx“,"編碼”)看成是解密的一個(gè)過(guò)程。
大家思考一下最后面的那個(gè)輸出可以得到正確的結(jié)果嗎?為什么?
下面我們來(lái)討論 ,通過(guò)http協(xié)議下的url傳輸后,編碼轉(zhuǎn)化問(wèn)題。
首先說(shuō)明的是本人本地默認(rèn)編碼是gbk。
我們只用Servlet,不使用任何框架比如spring(因?yàn)槭褂每蚣軙r(shí),框架也有一套自己自己的機(jī)制)如下代碼
public class HttpEncode extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String str = req.getQueryString();
System.out.println(req.getCharacterEncoding());
String encode = null;
try {
encode = req.getParameter("encode");
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(str);
System.out.println(encode);
}
}
我們分別用jetty(版本6.1)和resin(版本3.1.8)下容器,測(cè)試如下請(qǐng)求 127.0.0.1/test?encode=%B9%FE 其中%B9%FE為GBk的編碼的漢字”哈“
jetty容器下輸出為
resin下為:
null
encode=%B9%FE
null
換做127.0.0.1/test?encode=%E5%93%88 ,utf8編碼的”哈“
jetty和resin下都輸出如下
null
encode=%E5%93%88
哈
為什么會(huì)是這樣?
我們拿jetty分析,在jetty的源碼中,
public String getParameter(String name)
{
if (!_paramsExtracted)
extractParameters();
return (String) _parameters.getValue(name, 0);
}
對(duì)應(yīng)的
extractParameters(); 部分代碼
if (_queryEncoding==null)
_uri.decodeQueryTo(_baseParameters);
然后
public void decodeQueryTo(MultiMap parameters)
{
if (_query==_fragment)
return;
_utf8b.reset();
UrlEncoded.decodeUtf8To(_raw,_query+1,_fragment-_query-1,parameters,_utf8b);
}
也就是如果
_queryEncoding為null時(shí),默認(rèn)是用utf8進(jìn)行解碼的。而resin也不例外。
jetty中
_queryEncoding的值可以通過(guò)org.mortbay.jetty.Request.queryEncoding 這個(gè)屬性給賦值而resin采用的是req.getCharacterEncoding()中的值為標(biāo)準(zhǔn)。
要想在jetty下 127.0.0.1/test?encode=%B9%FE,獲取到正確的字符,代碼如下
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String str = req.getQueryString();
System.out.println(req.getCharacterEncoding());
req.setAttribute("org.mortbay.jetty.Request.queryEncoding", "gbk");
String encode = null;
try {
encode = req.getParameter("encode");
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(str);
System.out.println(encode);
}
resin下只需要
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String str = req.getQueryString();
req.setCharacterEncoding("gbk");
System.out.println(req.getCharacterEncoding());
String encode = null;
try {
encode = req.getParameter("encode");
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(str);
System.out.println(encode);
}
通過(guò)上面想說(shuō)明的是,不同的容器,默認(rèn)編碼的策略是不一致的。只要我們了解編碼的基礎(chǔ)知識(shí)。通過(guò)一些封裝就很容易掌控這個(gè)局面。