標籤:

struts原理與實踐(4)

Struts原理與實踐(4)- -本篇我們來討論一下struts的國際化編程問題,即所謂的i18n編程問題,這一篇我們討論其基礎部分。與這個問題緊密相關的是在各java論壇中被頻繁提及的中文亂碼問題,因為,英、美編程人員較少涉及到中文亂碼問題,因此,這方面的英文資料也是非常奇缺的,同時也很少找到這方面比較完整的中文資料,本文也嘗試對中文亂碼問題做一些探討。要解決上述問題,需要有一定的字符集方面的知識,下面,我們就先介紹字符集的有關情況:一、從ASCII到Unicode(UTF-8)電子計算機技術是從美國開始發展起來的,因為美國使用的文字為英文,美國規定的計算機信息交換用的字元編碼集是人們熟知的擴展ASCII碼,它以8bit位元組為單位存儲,ASCII的0-31及127為控制符,32-126為可見字元,包括所有的英文字母,阿拉伯數字和其他一些常見符號,128-255的ASCII碼則沒有定義。ASCII對英語國家是夠用了,但對其他西歐國家卻不夠用,因此,人們將ASCII擴展到0-255的範圍,形成了ISO-8859-1字符集。值得一提的是,因為考慮到程序中處理的信息大多是西文信息,因此有些WEB容器(如:Tomcat4.x)在處理所接收到的request字元串時,如果您沒指定request的編碼方式則系統就預設地採用ISO-8859-1,明白這一點對理解後面的問題會有幫助。相比西方的拼音文字,東方的文字(如中文)的字元數要大得多,根本不可能在一個位元組內將它們表示出來,因此,它們以兩個位元組為單位存儲,以中文國標字符集GB2312為例,它的第一個位元組為128-255。系統可以據此判斷,若第一個位元組大於127,則把與該位元組後緊接著的一個位元組結合起來共兩個位元組組成一個中文字元。這種由多個位元組存儲一個字元的字符集叫多位元組字符集(MultiByte Charsets),對應的象ASCII這種用一個位元組存儲一個字元的字符集叫單位元組字符集(SingleByte Charsets)。在GB2312字符集中,ASCII字元仍然用一個位元組存儲,換句話說該ASCII是該字符集的子集。GB2312隻包含數千個常用漢字,往往不能滿足實際需要,因此,人們對它進行擴展,這就有了我們現在廣泛使用的GBK字符集,GBK是現階段Windows及其他一些中文操作系統的預設字符集。它包含2萬多個字元,除了保持和GB2312兼容外,還包含繁體中文字,日文字元和朝鮮字元。值得注意的是GBK只是一個規範而不是國家標準,新的國家標準是GB18030-2000,它是比GBK包含字元更多的字符集。我國的台灣地區使用的文字是繁體字,其字符集是BIG5,而日本採用的字符集則是SJIS。它們的編碼方法與GB2312類似,它們的ASCII字元部分是兼容的,但擴展部分的編碼則是不兼容的,比如這幾種字符集中都有"中文"這兩個字元,但他們在各自的字符集中的編碼並不相同,這就是用GB2312寫成的網頁用BIG5瀏覽時,看到的是亂糟糟的信息的原因。可見,在字符集的世界裡,呈現給我們的是一個群雄割據的局面,各字符集擁有一塊自己的地盤。這給各國和各地區交換信息帶來了很大的困難,同時,也給國際化(本地化)編程造成了很大的麻煩。常言道:"分久必合",隨著國際標準ISO10646定義的通用字符集(Universal Character Set即UCS)的出現,使這種局面發生了徹底的改觀。UCS 是所有其他字符集標準的一個超集. 它保證與其他字符集是雙向兼容的. 就是說, 如果你將任何文本字元串翻譯到 UCS格式, 然後再翻譯回原編碼, 你不會丟失任何信息。UCS 包含了用於表達所有已知語言的字元。不僅包括拉丁語、希臘語、 斯拉夫語、希伯來語、阿拉伯語、亞美尼亞語和喬治亞語的描述、還包括中文、 日文和韓文這樣的象形文字、 以及平假名、片假名、 孟加拉語、 旁遮普語果魯穆奇字元(Gurmukhi)、 泰米爾語、印.埃納德語(Kannada)、Malayalam、泰國語、 寮國語、 漢語拼音(Bopomofo)、Hangul、 Devangari、Gujarati、Oriya、Telugu 以及其他數也數不清的語。對於還沒有加入的語言, 由於正在研究怎樣在計算機中最好地編碼它們, 因而最終它們都將被加入。ISO 10646 定義了一個 31 位的字符集。 然而, 在這巨大的編碼空間中, 迄今為止只分配了前 65534 個碼位 (0x0000 到 0xFFFD)。 這個 UCS 的 16位子集稱為 基本多語言面 (Basic Multilingual Plane, BMP)。 將被編碼在 16 位 BMP 以外的字元都屬於非常特殊的字元(比如象形文字), 且只有專家在歷史和科學領域裡才會用到它們。UCS 不僅給每個字元分配一個代碼, 而且賦予了一個正式的名字。 表示一個 UCS 值的十六進位數, 通常在前面加上 "U+", 就象 U+0041 代表字元"拉丁大寫字母A"。 UCS 字元 U+0000 到 U+007F 與 US-ASCII(ISO 646) 是一致的, U+0000 到 U+00FF 與 ISO 8859-1(Latin-1) 也是一致的。這裡要注意的是它是以16bit為單位存儲,即便對字母"A"也是用16bit,這是與前面介紹的所有字符集不同的地方。歷史上,在國際標準化組織研究ISO10646標準的同時,另一個由多語言軟體製造商組成的協會也在從事創立單一字符集的工作,這就是現在人們熟知的Unicode。幸運的是,1991年前後ISO10646和Unicode的參與者都認識到,世界上不需要兩個不同的單一字符集。他們合併雙方的工作成果,並為創立單一編碼表而協同工作。兩個項目仍都存在並獨立地公布各自的標準,都同意保持ISO10646和Unicode的碼錶兼容,並緊密地共同調整任何未來的擴展。這與當年在PC機上的操作系統MS-dos與PC-dos的情形有些相象。後面,我們將視ISO10646和Unicode為同一個東西。有了Unicode,字符集問題接近了完美的解決,但不要高興得過早。由於歷史的原因:一些操作系統如:Unix、Linux等都是基於ASCII設計的。此外,還有一些資料庫管理系統軟體如:Oracle等也是圍繞ASCII來設計的(從其8i的白皮書上介紹的設置系統字符集和欄位的字符集中可以間接地看到這一點)。在這些系統中直接用Unicode會導致嚴重的問題。用這些編碼的字元串會包含一些特殊的字元, 比如 『『 或 『/『, 它們在 文件名和其他 C 庫函數參數里都有特別的含義。 另外, 大多數使用 ASCII 文件的 UNIX 下的工具, 如果不進行重大修改是無法讀取 16 位的字元的。 基於這些原因, 在文件名, 文本文件, 環境變數等地方,直接使用Unicode是不合適的。在 ISO 10646-1 Annex R 和 RFC 2279 里定義的 UTF-8 (Unicode Transformation Form 8-bit form)編碼沒有這些問題。UTF-8 有以下一些特性:UCS 字元 U+0000 到 U+007F (ASCII) 被編碼為位元組 0x00 到 0x7F (ASCII 兼容)。 這意味著只包含 7 位 ASCII 字元的文件在 ASCII 和 UTF-8 兩種編碼方式下是一樣的。所有 >U+007F 的 UCS 字元被編碼為一個多個位元組的串, 每個位元組都有標記位集。 因此,ASCII 位元組 (0x00-0x7F) 不可能作為任何其他字元的一部分。表示非 ASCII 字元的多位元組串的第一個位元組總是在 0xC0 到 0xFD 的範圍里, 並指出這個字元包含多少個位元組。 多位元組串的其餘位元組都在 0x80 到 0xBF 範圍里。 這使得重新同步非常容易, 並使編碼無國界,且很少受丟失位元組的影響。UTF-8 編碼字元理論上可以最多到 6 個位元組長, 然而 16 位 BMP 字元最多只用到 3 位元組長。位元組 0xFE 和 0xFF 在 UTF-8 編碼中從未用到。通過,UTF-8這種形式,Unicode終於可以廣泛的在各種情況下使用了。在討論struts的國際化編程之前,我們先來看看我們以前在jsp編程中是怎樣處理中文問題以及我們經常遇到的。二、中文字元亂碼的原因及解決辦法java的內核是Unicode的,也就是說,在程序處理字元時是用Unicode來表示字元的,但是文件和流的保存方式是使用位元組流的。在java的基本數據類型中,char是Unicode的,而byte是位元組,因此,在不同的環節java要對位元組流和char進行轉換。這種轉換髮生時如果字符集的編碼選擇不當,就會出現亂碼問題。我們常見的亂碼大致有如下幾種情形:1、漢字變成了問號"?"2、有的漢字顯示正確,有的則顯示錯誤3、顯示亂碼(有些是漢字但並不是你預期的)4、讀寫資料庫出現亂碼下面我們逐一對它們出現的原因做一些解釋:首先,我們討論漢字變成問號的問題。Java中byte與char相互轉換的方法在sun.io包中。其中,byte到char的常用轉換方法是:public static ByteToCharConverter getConverter(String encoding);為了便於大家理解,我們先來做一個小實驗:比如,漢字"你"的GBK編碼為0xc4e3,其Unicode編碼是u4f60。我們的實驗是這樣的,先有一個頁面比如名為a_gbk.jsp輸入漢字"你",提交給頁面b_gbk.jsp。在b_gbk.jsp文件中以某種編碼方式得到"你"的位元組數組,再將該數組以某種編碼方式轉換成char,如果得到的char值是0x4f60則轉換是正確的。a_gbk.jsp的代碼如下:Input*b_gbk.jsp的代碼如下:在瀏覽器中打開a_gbk.jsp並輸入一個"你"字,點擊OK按鈕提交表單,則會出現如圖1所示的結果:

圖1從圖1可以看出,在b_gbk.jsp中這樣將byte轉換為char是正確的,即得到的char是u4f60。這裡要注意的是:byte b[]=a.getBytes("ISO8859-1");中的編碼是ISO8859-1,這就是我們前面提到的有些web容器在您沒有指定request的字符集時它就採用預設的ISO8859-1。從圖1中我們還看到表達式 中的a並沒有正確地顯示"你"而是變成"??"這是什麼原因呢?這裡的a是作為一個String被顯示的,我們來看看我們常用的String構造函數:String(byte[] bytes,String encoding);在國標平台上,該函數會認為bytes是按GBK編碼的,如果後一個參數省略,它也會認為是encoding是GBK。對前一個參數就相當於將b_gbk.jsp文件的這句byte b[]=a.getBytes("ISO8859-1");中的ISO8859-1改為GBK,這樣顯然在GBK字符集中找不到相應的目的編碼,它給出的結果是0x3f、0x3f。因此,就會顯示為"??",這也就是造成亂碼的第一種現象的原因。我們的例子是演示的從byte到char的轉換過程,相反的過程也會造成同樣的問題,限於篇幅,就不在此討論了,大家自己可以做類似的實驗來驗證。解決該問題的方法就是象例子中a1那樣,在獲取byte數組時,指定編碼為ISO8859-1。接下來,我們討論有些漢字能正常顯示,有些不能正常顯示的問題。如果我們將String a1=new String(a.getBytes("ISO8859-1"),"GBK");中的GBK改為GB2312則象朱鎔基的"鎔"字就不能正常顯示,這是因為該字是GBK中的字元而在GB2312中不存在。解決上述兩種問題的方法就是象a1那樣構造String,也就是人們常說的同時也是常用的轉碼的方法。採用這種方法會在程序中到處出現這種語句,特別是在Struts中,Struts有一個回寫表單的功能,在回寫時也要做這種轉換,這樣的語句差不多要多一倍。因此,這是個比較笨拙的方法,有沒有簡捷一些的方法呢?其實是有的,只要在取得request的字元串前加上request.setCharacterEncoding("GBK");這句,指定request的字符集。則 中的a就能正常顯示,a1反而不能正常顯示。此時要將byte b[]=a.getBytes("ISO8859-1");中的ISO8859-1變成GBK,從byte到char的轉換才是正確的,這就是此時a能正常顯示而a1反而不能正常顯示的原因。如果此時要a1正常顯示則必須將String a1=new String(a.getBytes("ISO8859-1"),"GBK");中的ISO8859-1改為GBK。很顯然,使用request.setCharacterEncoding("GBK");只能解決GBK字元問題,要解決i18n問題則要使用UTF-8來取代GBK。我們接著做上述實驗,將a_gbk.jsp和b_gbk.jsp分別另存為a.jsp和b.jsp將文件中的GBK改為UTF-8,更改後的代碼分別如下:a.jsp代碼:Input*b.jsp代碼:再在a.jsp中輸入"你"字,你會發現顯示結果中,一個漢字是用三個byte表示的,它們的值分別是0xe4、0xbd、0xa0,也就是說用UTF-8來表示漢字,每個漢字要比GBK多佔用一個byte,這也是使用UTF-8要多付出的一點代價吧。現在,我們討論一下第三個問題,即顯示亂碼,有些莫名其妙的漢字並不是你預期的結果。在上例中將String a1=new String(a.getBytes("UTF-8"),"UTF-8");改為String a1=new String(a.getBytes("UTF-8"),"GBK");再輸入"你"字,則a1會顯示成"浣?",您只要看一看"浣"的UTF-8碼和GBK碼就會知道其中的奧秘了。下面,我們討論一下最後一個問題,就是讀寫資料庫時出現亂碼。現在一些常用的資料庫都支持資料庫encoding,也就是說在創建資料庫時可以指定它自己的字符集設置,資料庫數據以指定的編碼形式存儲。當應用程序訪問資料庫時,在入口和出口處都會有encoding轉換。如果,在應用程序中字元本來已變成了亂碼,當然也就無法正確地轉換為資料庫的字符集了。資料庫的encoding可根據需要來設置,比如要支持簡、繁體中文、日、韓、英語選GBK,如果還要支持其他語言最好選UTF-8。本篇文章對字符集及中文亂碼問題做了一下探討,為實現國際化編程的實踐打下一個基礎。下一篇文章,我們將介紹struts中實現國際化編程的具體步驟,並將我們前面介紹的登錄例子進行國際化。參考文獻:UTF-8 and Unicode FAQ《JSP動態網站技術入門與提高》太陽工作室 孫曉龍 趙莉編著
推薦閱讀:

太極拳的理法與實踐方法
2.如何是「歸依在三寶,實踐在三寶,成就在三寶?」
默照禪的理論與實踐
秋英多傑仁波切:正法實踐論(連載二十三)
如何降壓、控糖、護腰?79歲國醫大師親身實踐,總結成3句話告訴你!

TAG:原理 | 實踐 |