重拾Java(02) - 字符編碼

ASCII碼

在計算機內(nèi)部授嘀,所有的信息最終都表示為一堆二進制形式的數(shù)據(jù)。每一個二進制位(bit)有0和1兩種狀態(tài)给郊,因此八個二進制位就可以組合出256種狀態(tài)菩浙,稱為一個字節(jié)(byte),從0000000到11111111睬愤。上世紀60年代片仿,美國制定了一套字符編碼,對英語字符與二進制位之間的關(guān)系做了統(tǒng)一規(guī)定尤辱,稱之為ASCII碼(American Standard Code for Information Interchange)并沿用至今砂豌。ASCII碼一共規(guī)定了128個字符的編碼厢岂,比如空格是32(00100000),大寫的字母A是65(01000001)阳距。這128個符號(包括32個不能打印出來的控制符號)塔粒,只占用了一個字節(jié)的后面7位,最前面的1位統(tǒng)一規(guī)定為0筐摘。
??英語用128個符號編碼就夠了卒茬,但是其他語言很多都不止128個符號,比如在法語中蓄拣,字母上方有注音符號扬虚,這種字符就無法用ASCII碼表示。為此球恤,在某些歐洲國家會利用字節(jié)中閑置的最高位編入新的符號辜昵,例如法語中的é的編碼為130(二進制10000010)。這樣一來咽斧,這些歐洲國家使用的編碼體系堪置,可以表示最多256個符號。但是這里又出現(xiàn)了新的問題张惹,不同的國家有不同的字母舀锨,因此,哪怕它們都使用256個符號的編碼方式宛逗,代表的字母卻不一樣坎匿。比如,130在法語編碼中代表了é雷激,在希伯來語編碼中卻代表了字母?替蔬,但是不管怎樣,所有這些編碼方式中屎暇,0127表示的符號是一樣的承桥,不一樣的只是128255的這一段。
??至于亞洲國家的文字根悼,使用的符號就更多了凶异,1994年由中華書局、中國友誼出版公司出版的《中華字杭费玻》就收錄了85568個漢字剩彬。一個字節(jié)只能表示256種符號肯定是不夠的,就必須使用多個字節(jié)表達一個符號矿卑。比如襟衰,簡體中文常見的編碼GB2312使用兩個字節(jié)表示一個漢字,所以理論上最多可以表示65536個符號。

Unicode

世界上存在著多種編碼方式瀑晒,同一個二進制數(shù)字可以被解釋成不同的符號绍坝。因此,要想打開一個文本文件苔悦,就必須知道它的編碼方式轩褐,否則用錯誤的編碼方式解讀,就會出現(xiàn)亂碼玖详“呀椋可以想象,如果有一種編碼蟋座,將世界上所有的符號都納入其中拗踢。每一個符號都給予一個獨一無二的編碼,那么亂碼問題就會消失向臀,這就是Unicode巢墅。Unicode是一個很大的集合,現(xiàn)在的規(guī)娜颍可以容納100多萬個符號君纫。每個符號的編碼都不一樣,比如芹彬,U+0041表示英語的大寫字母A蓄髓,U+660A表示漢字"昊"。需要注意的是舒帮,Unicode只是一個符號集会喝,它只規(guī)定了符號的二進制代碼,卻沒有規(guī)定這個二進制代碼應(yīng)該如何存儲玩郊。
比如肢执,漢字"昊"的Unicode是十六進制數(shù)660A,轉(zhuǎn)換成二進制數(shù)是0110 0110 0000 1010瓦宜,也就是說這個符號的表示至少需要2個字節(jié)。表示其他更大的符號岭妖,可能需要3個字節(jié)或者4個字節(jié)临庇。這里就有兩個問題,第一個問題是昵慌,如何才能區(qū)別Unicode和ASCII假夺?計算機怎么知道三個字節(jié)表示一個符號,而不是分別表示三個符號呢斋攀?第二個問題是已卷,我們已經(jīng)知道,英文字母只用一個字節(jié)表示就夠了淳蔼,如果Unicode統(tǒng)一規(guī)定侧蘸,每個符號用三個或四個字節(jié)表示裁眯,那么每個英文字母前都必然有二到三個字節(jié)是0,這對于存儲來說是極大的浪費讳癌,文本文件的大小會因此大出二三倍穿稳,這是無法接受的。為此出現(xiàn)了Unicode的多種存儲方式晌坤,也就是說有多種不同的二進制格式可以用來表示Unicode逢艘。

UTF-8

UTF-8就是在互聯(lián)網(wǎng)上使用最廣的一種Unicode的實現(xiàn)方式。其他實現(xiàn)方式還包括UTF-16(字符用兩個字節(jié)或四個字節(jié)表示)和UTF-32(字符用四個字節(jié)表示)骤菠,不過在互聯(lián)網(wǎng)上基本不用它改。UTF-8最大的一個特點,就是它是一種變長的編碼方式商乎。它可以使用1~4個字節(jié)表示一個符號央拖,根據(jù)不同的符號而變化字節(jié)長度。UTF-8的編碼規(guī)則很簡單截亦,只有二條:

  1. 對于單字節(jié)的符號爬泥,字節(jié)的第一位設(shè)為0,后面7位為這個符號的Unicode碼崩瓤。因此對于英語字母袍啡,UTF-8編碼和ASCII碼是相同的。
  2. 對于n字節(jié)的符號(n>1)却桶,第一個字節(jié)的前n位都設(shè)為1境输,第n+1位設(shè)為0,后面字節(jié)的前兩位一律設(shè)為10颖系。剩下的沒有提及的二進制位嗅剖,全部為這個符號的Unicode碼。
    下表總結(jié)了編碼規(guī)則嘁扼,字母x表示可用編碼的位信粮。
Unicode符號范圍 UTF-8編碼方式
0000 0000-0000 007F 0xxxxxxx
0000 0080-0000 07FF 110xxxxx 10xxxxxx
0000 0800-0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

說明:解讀UTF-8編碼非常簡單。如果一個字節(jié)的第一位是0趁啸,則這個字節(jié)單獨就是一個字符强缘;如果第一位是1,則連續(xù)有多少個1不傅,就表示當前字符占用多少個字節(jié)旅掂。

Java中的編解碼

I/O操作時的編解碼

在進行I/O操作時經(jīng)常會遇到將字節(jié)流轉(zhuǎn)換成字符流的場景,Java的API提供了InputStreamReader和OutputStreamWriter來解決這樣的問題访娶,而這兩個類的構(gòu)造器中都可以指定編碼/解碼的方式商虐。

InputStreamReader(InputStream in)  // 使用默認的字符集
InputStreamReader(InputStream in, String charsetName)  throws UnsupportedEncodingException
InputStreamReader(InputStream in, Charset cs)
InputStreamReader(InputStream in, CharsetDecoder dec)
OutputStreamWriter(OutputStream out)  // 使用默認的字符集
OutputStreamWriter(OutputStream out, String charsetName) throws UnsupportedEncodingException
OutputStreamWriter(OutputStream out, Charset cs)
OutputStreamWriter(OutputStream out, CharsetEncoder enc)

從JDK 1.4引入了NIO開始,我們可以使用Charset類提供encode和decode方法實現(xiàn)字符數(shù)組和字節(jié)數(shù)組的轉(zhuǎn)換,代碼如下所示:

Charset cs = Charset.forName("utf-8");
String str = "駱昊";
ByteBuffer buffer1 = cs.encode(str);
// 駱                           昊
// e9        aa        86       e6       98       8a
// 11101001  10101010  10000110 11100110 10011000 10001010
for (int index = 0; index < buffer1.limit(); index += 1) {
    System.out.print(Integer.toHexString(buffer1.get(index) & 0xff) + " ");
}
System.out.println();
CharBuffer buffer2 = cs.decode(buffer1);
// 駱昊
System.out.println(buffer2.toString());

字符串的編解碼

Java中的String類提供了用字節(jié)數(shù)組和指定的編碼構(gòu)造字符串對象的操作秘车,同時也提供了將字符串按照指定的編碼解碼成字節(jié)數(shù)組的操作典勇,下面我們來做幾個小實驗。

實驗1:中文變成'?'鲫尊。

public static void main(String[] args) throws UnsupportedEncodingException {
    String str = "hello, 駱昊";
    byte[] buffer = str.getBytes("iso-8859-1");
    // hello, ??
    System.out.println(new String(buffer));
}

說明:ISO-8859-1是單字節(jié)編碼痴柔,中文“駱昊”的編碼(0x9a86和0x660a)會被轉(zhuǎn)換成0x3f疫向,而0x3f是ASCII碼中的'?'咳蔚,所以中文就變成了問號,而且中文字符的編碼信息已經(jīng)丟失搔驼,再怎么解碼也沒有機會還原出原來的中文字符了谈火。所以這種現(xiàn)象也稱之為“編碼黑洞”,因為它把不認識的字符給吞噬掉了舌涨。很多Java的框架和產(chǎn)品默認都使用了ISO-8859-1糯耍,所以這個問題很常見。

實驗2:中文變成看不懂的字符囊嘉。

public static void main(String[] args) throws UnsupportedEncodingException {
    String str = "hello, 駱昊";
    byte[] buffer = str.getBytes("gbk");
    // hello, ??ê?
    System.out.println(new String(buffer, "iso-8859-1"));   
}

說明:這種情況在使用瀏覽器的時候也很常見温技,服務(wù)器傳過來的是中文字符但是瀏覽器的編碼卻設(shè)置為ISO-8859-1就會出這種問題。

如果中文經(jīng)過了多次編解碼扭粱,那么還有可能遇到一個中文字符變成多個問號的情況舵鳞。其實要解決這些編碼問題原則非常簡單,首先如果要表示中文字符就不能使用單字節(jié)編碼琢蛤,這樣勢必會出現(xiàn)“黑洞”蜓堕;其次編碼和解碼使用的“碼”應(yīng)當是一致的。

URL編碼

URL是統(tǒng)一資源定位符(Universal Resource Locator)的縮寫博其,是Internet上標準的資源地址套才。它最初是由萬維網(wǎng)和瀏覽器的發(fā)明者英國人Tim Berners-Lee發(fā)明用來作為萬維網(wǎng)的地址,現(xiàn)在已經(jīng)被W3C編制為Internet標準(RFC 1738)慕淡。統(tǒng)一資源定位符的標準格式如下:

協(xié)議://服務(wù)器域名或地址:[端口號]/資源路徑/文件名[?查詢參數(shù)]

我們試一試在用谷歌搜索“駱昊”背伴,來看看瀏覽器地址欄中的URL到底是什么樣的。

https://www.google.com.hk/#safe=strict&q=%E9%AA%86%E6%98%8A

URL中允許出現(xiàn)的字符分為保留字符(有特殊含義的字符)與未保留字符峰髓,未保留字符包括英文大小寫字母傻寂、0-9的數(shù)字以及‘-’、 ‘_’儿普、 ‘.’和'~'崎逃,保留字符包括 ‘!’掷倔、 ‘*’眉孩、 ‘'’、 ‘(’、 ‘)’浪汪、 ‘;’巴柿、 ‘:’、 ‘@’死遭、 ‘&’广恢、 ‘=’、 ‘+’呀潭、 ‘$’钉迷、 ‘,’、 ‘/’钠署、 ‘?’糠聪、 ‘#’、 ‘[’和‘]’谐鼎。如果URL中需要用到保留字符或者非URL允許的字符則需要使用百分號編碼舰蟆,例如:‘=’要處理成‘%3D’、‘+’要處理成‘%2B’狸棍、而上面要搜索的‘駱’和‘昊’兩個中文字符被處理成了百分號編碼的‘%E9%AA%86’和‘%E6%98%8A’身害。

Java中要將URL中的非URL允許字符處理成百分號編碼有非常簡單的辦法,就是使用URLEncoder類的encode方法草戈,代碼如下所示塌鸯。

public static void main(String[] args) throws UnsupportedEncodingException {
    String urlStr = "Java 駱昊";
    String encodedUrlStr = URLEncoder.encode(urlStr, "utf-8");
    // Java+%E9%AA%86%E6%98%8A
    System.out.println(encodedUrlStr);
}

幾種編碼格式的比較

表示中文可以選擇的編碼方式很多,包括GB2312猾瘸、GBK界赔、GB18030、UTF-8和UTF-16牵触。UTF-16定義了Unicode字符在計算機中的存取方式淮悼,用固定長度的兩個字節(jié)來表示所有的字符,Java中的char類型之所以是兩個字節(jié)就是因為Java使用了UTF-16作為內(nèi)存中字符存儲的格式揽思。UTF-16的編碼效率高袜腥,字符與字節(jié)之間的轉(zhuǎn)換也相對簡單,但是如果在網(wǎng)絡(luò)上傳輸數(shù)據(jù)的話會遇到大尾數(shù)和小尾數(shù)字節(jié)順序轉(zhuǎn)換的問題钉汗,因此UTF-8更適合在網(wǎng)絡(luò)上傳輸數(shù)據(jù)羹令,而UTF-16更適合在內(nèi)存中使用。UTF-8使用了變長存儲的方式损痰,對ASCII字符采用單字節(jié)存儲福侈,對其他字符可以使用1~6個字節(jié)來表示,編碼效率介于GBK和UTF-16之間卢未,因此開發(fā)Java Web應(yīng)用時肪凛,強烈建議使用UTF-8這種編碼方式堰汉。

推薦閱讀

  1. ASCII - https://zh.wikipedia.org/wiki/ASCII
  2. Unicode - https://zh.wikipedia.org/wiki/Unicode
  3. UTF-8 - https://zh.wikipedia.org/wiki/UTF-8
  4. 中文編碼 - https://zh.wikipedia.org/wiki/%E5%AD%97%E7%AC%A6%E7%BC%96%E7%A0%81
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市伟墙,隨后出現(xiàn)的幾起案子翘鸭,更是在濱河造成了極大的恐慌,老刑警劉巖戳葵,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件就乓,死亡現(xiàn)場離奇詭異,居然都是意外死亡拱烁,警方通過查閱死者的電腦和手機生蚁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來戏自,“玉大人守伸,你說我怎么就攤上這事∑滞” “怎么了尼摹?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長剂娄。 經(jīng)常有香客問我蠢涝,道長,這世上最難降的妖魔是什么阅懦? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任和二,我火速辦了婚禮,結(jié)果婚禮上耳胎,老公的妹妹穿的比我還像新娘惯吕。我一直安慰自己,他們只是感情好怕午,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布废登。 她就那樣靜靜地躺著,像睡著了一般郁惜。 火紅的嫁衣襯著肌膚如雪堡距。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天兆蕉,我揣著相機與錄音羽戒,去河邊找鬼。 笑死虎韵,一個胖子當著我的面吹牛易稠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播包蓝,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼驶社,長吁一口氣:“原來是場噩夢啊……” “哼呆奕!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起衬吆,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎绳泉,沒想到半個月后逊抡,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡零酪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年冒嫡,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片四苇。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡孝凌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出月腋,到底是詐尸還是另有隱情蟀架,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布榆骚,位于F島的核電站片拍,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏妓肢。R本人自食惡果不足惜捌省,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望碉钠。 院中可真熱鬧纲缓,春花似錦、人聲如沸喊废。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽污筷。三九已至褂策,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間颓屑,已是汗流浹背斤寂。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留揪惦,地道東北人遍搞。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像器腋,于是被迫代替她去往敵國和親溪猿。 傳聞我的和親對象是個殘疾皇子钩杰,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 字符集和編碼簡介 在編程中常常可以見到各種字符集和編碼诊县,包括ASCII,MBCS,Unicode等字符集讲弄。確切的說...
    蘭山小亭閱讀 8,453評論 0 13
  • 大概每個人在使用軟件時都遇到過亂碼的問題,這是由于字符的編碼和解碼方式不一致導(dǎo)致依痊,我們知道計算機只認識二進制數(shù)據(jù)避除,...
    楚客閱讀 1,416評論 1 9
  • 每每看到好看的花朵,都是十分喜歡胸嘁,于是也有了要養(yǎng)一顆的想法瓶摆。打聽后,打消了念頭性宏,還是養(yǎng)盆綠葉的吧群井。 只需澆水,沒有...
    蜜覓奇點閱讀 426評論 0 1
  • 1. 浮動元素有什么特征毫胜?對父容器书斜、其他浮動元素、普通元素酵使、文字分別有什么影響? 浮動元素特征: 浮動元素會脫離正...
    billa_8f6b閱讀 218評論 0 0
  • 有一種守候靜默無言我以為你會明白那里有我深藏的熱情如冬夜的爐火而你怎能視而不見 有一種等待含蓄矜持我多希望你會明白...
    紅塵久客閱讀 820評論 16 11