冰浪

          哥已不再年輕 - 堅定夢想,畢生追求!
          posts - 85, comments - 90, trackbacks - 0, articles - 3
            BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理

          JavaME中文編碼

          Posted on 2009-04-09 09:38 冰浪 閱讀(223) 評論(0)  編輯  收藏 所屬分類: J2ME
          1. 簡介

          本文介紹JavaME中文編碼的相關問題,這個問題一度是互聯網上的開發者們討論的熱門話題。本文整理和綜合了網上眾多相關內容,盡可能的為開發者提供一個全面、系統的認識。

          文中的代碼僅用來說明原理,可能很不完整,缺乏變量定義或者返回值,請諒解。部分代碼直接來源于網上的其他資料。

          感謝眾多開發者在中文編碼問題上做出的努力與探索。總結中有什么問題的話,歡迎大家指正:)

           

           2. 術語介紹

           2.1 ASCII

          基于羅馬字母表的一套電子計算器編碼系統,是單字節的編碼方式,每個ASCII字符占用1個字節(8bits),所以ASCII編碼最多可以表示256個字符。它是美國信息交換標準委員會(American Standards Committee for Information Interchange)的縮寫, 為美國英語通信所設計。

          顯然ASCII編碼用來表示英文字母和字符是足夠了的,但是對于中文和日文等眾多的文字來說,是遠遠不夠的。

          跟ASCII類似的編碼還有ISO8859-1。

          2.2 UNICODE

          雙字節編碼方式,它為每種語言中的每個字符設定了統一并且唯一的二進制編碼,以滿足跨語言、跨平臺進行文本基準轉換、處理的要求。UNICODE支持歐洲、非洲、中東、亞洲(包括統一標準的東亞像形漢字和韓國像形文字)的文字。

          UNICODE又可以分為“高位在前”和“低位在前”的兩種格式,這和CPU的處理方式有點關系。這一點可以通過BOM(Byte Order Mark)來標示,若采用 “低位在前”方式編碼,BOM 會表示為 0xFF 0xFE,而在 Unicode 的定義中是不存在 U+FFFE 這個字符的。若采用高位在前方式編碼,BOM 會表示為 0xFE 0xFF,而 U+FEFF 剛好是在 Unicode 中的有效字符,代表的是一個不占空間的 space 符號,所以即使沒被解釋為 BOM,也不會對閱覽者產生錯誤的信息。

          但UINICODE也帶來一些問題,當美國人看見自己每天最常用的字符需要用兩倍的空間來保存時,自然會覺得這是一種浪費,他們一定會說:看看那堆0。于是新的編碼方式又誕生了。

           2.3 UTF-8

          UTF的全稱是UCS Transformation Format,即把Unicode轉做某種格式的意思。目前存在的UTF格式有:UTF-7, UTF-7.5, UTF-8, UTF-16, 以及 UTF-32,本文討論UTF-8格式。

          UTF-8是UNICODE的一種變長字符編碼,理論上使用1~6個字節來編碼UNICODE。

          雖然理論上UTF-8最多為6個字節,但是,由于雙字節的Unicode最大為0XFFFF,所以雙字節的Unicode轉為UTF-8后最長為3個字節。

          下列字節串用來表示一個字符。 用到哪個串取決于該字符在 Unicode 中的序號。

          U-00000000 - U-0000007F: 0xxxxxxx
          U-00000080 - U-000007FF: 110xxxxx 10xxxxxx
          U-00000800 - U-0000FFFF: 1110xxxx 10xxxxxx 10xxxxxx
          U-00010000 - U-001FFFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
          U-00200000 - U-03FFFFFF: 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
          U-04000000 - U-7FFFFFFF: 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

          上表中的xxx為Unicode編碼的二進制數據。例如:“中”字,Unicode編碼為4E2D。

          Unicode: 4E 2D 	        01001110 00101101
          UTF-8: E4 B8 AD 	11100100 10111000 10101101

          對于英文來說,UTF-8跟ISO8859-1一樣節約;但顯然中文等字符將為UTF-8付出更多。

          2.4 GB2312

          GB2312又稱國標碼,由國家標準總局發布,1981年5月1日實施,通行于大陸。新加坡等地也使用此編碼。它是一個簡化字的編碼規范,當然也包括其他的符號、字母、日文假名等,共7445個圖形字符,其中漢字占6763個。我們平時說6768個漢字,實際上里邊有5個編碼為空白,所以總共有6763個漢字。

          GB2312規定“對任意一個圖形字符都采用兩個字節表示,每個字節均采用七位編碼表示”,習慣上稱第一個字節為“高字節”,第二個字節為“低字節”。GB2312中漢字的編碼范圍為,第一字節0xB0-0xF7(對應十進制為176-247),第二個字節0xA0-0xFE(對應十進制為160-254)。

          GB2312具有很多擴展,其中GBK是微軟對GB2312的擴展,GB18030則是2000年發布的國家標準,是到目前為止最新的國標漢字編碼。

           3.JavaME中的字符編解碼

           3.1 編解碼方法

          說到JavaME中的字符編碼問題,自然要從String類入手,在String類中我們可以找到字符編解碼的相關方法:

          1. 解碼:

          public String(byte[] bytes, String enc) throws UnsupportedEncodingException

          2. 編碼:

          public byte[] getBytes(String enc) throws UnsupportedEncodingException

          舉例來說,“諾基亞”三個漢字的GB2312編碼為C5 B5 BB F9 D1 C7。

          代碼段一,解碼試驗:

          byte[] codes = {0XC5, 0XB5, 0XBB, 0XF9, 0XD1, 0XC7};
          String string = new String(codes, “gb2312”);
          testForm.append(string);

          得到的結果為:諾基亞

          代碼段二,編碼試驗:

          byte[] codes = “諾基亞”.getBytes(“gb2312”);
          for (int i = 0, t = codes.length; i < t; i ++) {
          String hexByte = Integer.getHexString(codes[i]);
          if (hexByte.length() > 2) {
          hexByte = hexByte.subString(hexByte.length() - 2);
          }
          testForm.append(“0X”+ hexByte.subString + “, ”);
          }

          得到的結果為:0XC5, 0XB5, 0XBB, 0XF9, 0XD1, 0XC7,

           3.2 檢查設備的編碼支持情況

          一個最直接的獲取編碼支持的方法是使用System.getProperty(“microedition.encoding”),可以得到設備的默認的字符編碼,以NOKIA設備為例,得到的屬性值為ISO8859-1。

          然而,通過這個方法的意義并不大。首先,它只能獲取到一個編碼格式,而一般設備都會支持很多種編碼規范;其次,這個屬性的數值與虛擬機的實現有很大關系,同樣以NOKIA S40v2為例,不論設備的目標市場使用什么語言,這個屬性統一為ISO8859-1[參考資料5],顯然ISO8859-1對于中文來說是毫無意義的。

          再回過頭來看看上一節中的兩個方法,它們都會拋出一個UnsupportedEncodingException。利用這一點,我們可以自己來做一個設備支持編碼規范情況的測試。

          boolean isEncodingSupported (String encoding) {
          try {
          "諾基亞".getBytes(encoding);
          return true;
          } catch (UnsupportedEncodingException uee) {
          return false;
          }
          }

          這里需要提醒的是,對于同一個編碼格式來說,可能會有很多種不同的名稱,例如Unicode在NOKIA的設備上用的是ucs-2,再例如utf-8來說,utf-8、utf8和utf_8都會有可能。對于這一點,CLDC的規范中并沒有給出嚴格的定義。所以在實際測試的過程中需要充分考慮到這個情況。

          3.3 readUTF() 和 writeUTF(String)

          CLDC中還有兩個方法跟字符編碼有關系:DataInputStream中的readUTF()和DataOutputStream中的writeUTF(String)。根據兩個方法的Java Doc,writeUTF(String)首先會向輸出流中寫入字符串編碼成UTF8格式后的byte數組長度(2個字節),然后再將這個UTF8的byte數組寫入。而readUTF()則是先從輸入流中讀取2個字節,組成一個short數值,在從輸入流中讀出這個數值長度的byte數據,再將這個byte數組解碼成字符串。詳細說明請參考DataInputStream和DataOutputStream的Java Document。

           4.具體問題的解決

          上一部分中介紹了CLDC平臺上所有跟字符編解碼相關的API。了解了這些內容以后,就可以結合具體的情況來考慮如何解決中文編碼的問題了。

          首先,中文編碼問題中最常見的情況就是亂碼,那么亂碼是如何產生的呢?無非是以下幾種情況:

          1. 指定的編碼格式與數據實際的編碼格式不符,造成數據被編碼成指定的替代符號或被解釋成與源字符完全不同的字符;
          2. 指定的編碼格式正確,數據不完整或被篡改,造成數據無法在字符集中找到與其對應的編碼;

          所以,解決中文編碼問題的幾個基本思路是:

          1. 存儲或傳輸前,確保數據被正確編碼;
          2. 確保存儲、讀取和傳輸的過程完整、正確;
          3. 解碼時,使用與編碼時同樣的編碼格式;
          4. 確認編碼格式是否被設備支持;

           

          4.1 RMS存儲的中文問題

          這個問題完全可以使用readUTF和writeUTF來解決。

          用UTF8編碼向RecordStore中寫入中文:

          String content = "中文字符";
          ByteArrayOutputStream bos = new ByteArrayOutputStream();
          DataOutputStream dos = new DataOutputStream(bos);
          dos.writeUTF(content);
          byte[] bytes = bos.toByteArray();
          rs.addRecord(bytes, 0, bytes.length);

          從RecordStore中讀出UTF8編碼的中文:

          byte utfBytes[] = rs.getRecord(dbid);
          DataInputStream dis=new DataInputStream(new ByteArrayInputStream(utfBytes));
          String content = dis.readUTF();

          當然,使用UTF8對于中文來說意味著更大的存儲空間,所以也可以使用類似Unicode和GB2312等2字節的編碼。在此之前,請通過3.2中描述的方式檢測編碼是否被支持。

          用GB2312編碼向RecordStore中寫入中文:

          String content = "中文字符";
          byte[] bytes = content.getBytes("gb2312");
          rs.addRecord(bytes, 0, bytes.length);

          從RecordStore中讀出GB2312編碼的中文:

          byte gb2312Bytes[] = rs.getRecord(dbid);
          String content = new String(gb2312Bytes, "gb2312");

           4.2 從Resource文件中讀取中文

          大概可以分為兩種情況,一是這個文件遵循某種特定的格式,例如RPG游戲的關卡文件,其中包含地圖、事件和對話等數據,文件由特定的程序生成,為自有格式。這種情況基本上可以使用與上一小節中同樣的方式來解決。關鍵是,存儲和讀取的過程遵從同樣的數據和編碼格式。

          另一種情況是txt文件,可能通過一些文本編輯工具生成。Txt文件中常見的編碼格式有Unicode、UTF8、Unicode Big Endian等。在我們讀取txt文件之前,最先要確認的就是這個txt文件所使用的編碼格式。

          以UTF8為例:

          in = getClass().getResourceAsStream(filename);
          in.read(word_utf);
          in.close();
          string =new String(word_utf,"UTF-8");

          對于Unicode來說,這里引用了參考1中的一段代碼,這段代碼實際上是在處理“低位在前”的Unicode:

          public static String unicodeBytesToString(byte abyte0[], int i)
          {
          StringBuffer stringbuffer = new StringBuffer("");
          for(int j = 0; j < i; )
          {
          int k = abyte0[j++]; //注意在這個地方進行了碼制的轉換
          if(k < 0)
          k += 256;
          int l = abyte0[j++];
          if(l < 0)
          l += 256;
          char c = (char)(k + (l << 8));//把高位和低位數組裝起來
          stringbuffer.append(c);
          }
          }

          對于高位在前的Unicode,可以使用和UTF8類似的方式。請注意,這里的"ucs-2"是針對諾基亞設備的,其他廠商設備可能與此不同,請查閱相關文檔或自行測試。

          in = getClass().getResourceAsStream(filename);
          in.read(word_ unicode);
          in.close();
          string =new String(word_unicode, "ucs-2");

          實際上經過我在NOKIA設備上的測試,對于低位在前的Unicode,也可以使用這個方式。前提是需要確保在數據的最前端添加低位在前的BOM(0XFFFE)。

          繼續使用“諾基亞”為例,高位在前和低位在前的的Unicode編碼分別為:

          nokiaBE = {(byte)0x8b, (byte)0xfa, (byte)0x57, (byte)0xfa, (byte)0x4e, (byte)0x9a,}
          nokiaLE = {(byte)0xfa, (byte)0x8b, (byte)0xfa, (byte)0x57, (byte)0x9a,  (byte)0x4e,};

          new String(nokiaBE, "ucs-2")的結果是“諾基亞”,而new String(nokiaLE, "ucs-2")的結果則是亂碼。然后,我們對nokiaLE做出修改:

          nokiaLE = {(byte)0xff, (byte)0xfe,
          (byte)0xfa, (byte)0x8b, (byte)0xfa, (byte)0x57, (byte)0x9a,  (byte)0x4e,};

          修改后,再次執行new String(nokiaLE, "ucs-2",則得到的結果也是“諾基亞”。

          BTW,對于Resouce文件來說,雖然使用Unicode編碼存儲中文看起來像是比UTF-8要更節約,但是當Resource資源被打成Jar包時,壓縮后的文件大小可能很接近。

           4.3通過網絡讀取中文

          和前面描述的一樣,避免亂碼的關鍵同樣是保證編解碼使用同樣的格式,也就是客戶端與服務器段保持同樣的字符編碼。

          對于socket連接來說,傳輸的內容可以使用自定義的數據格式,所以處理的方式完全和前面兩節是相同的,甚至可以向http學習,在數據的開頭約定字符編碼。

          對于http連接,在http數據頭中已經約定了編碼格式,使用這個編碼格式解碼即可。另外一個很常見的問題,就是從xml文件中解析中文。對于這一點,在kxml2中已經有了很好的解決方案。

          org.kxml2.io.KXmlParser.setInput(InputStream is, String _enc)

          你可以通過_enc指定一個編碼格式,如果_enc為null,則Parser會根據數據的特性自動嘗試各種編碼格式。由于kxml為開源項目,如果這里的處理方式需要調整,你也可以自己動手去完善它的功能。

          在kxml2中也增加了對wml文件的解析,有興趣的可以研究一下,這一部分我沒有作過嘗試。

           4.4 JAD中的中文

          JAD文件如果需要使用中文,則需要使用UTF8格式。關于如何在JAD中寫入UTF8數據,可以有很多方法,可以使用一些Ultral Editor之類的文本工具,或使用一些JAD/MENIFEST生成工具。你甚至可以自己編寫一個JAD/MENIFEST的生成工具,對于JavaSE平臺來說,這并不是一件太難的工作,你還可以給這個工具賦予更多的功能,例如自動填寫vendor和version之類的字段。

          特別說明,對于NOKIA Series 40v2等設備來說,通過OTA下載Jad/Jar時,需要在服務器端為JAD添加MIME type時標明encoding為UTF8,否則即使你的JAD確實使用了UTF8格式,安裝之后仍然會是亂碼。

          4.5 Unicode和UTF8的互轉

          在網上有一些Unicode和UTF8互相轉換的代碼,是直接通過編碼格式去實現的,從前文敘述的Unicode和UTF8之間的關系來看,Unicode和UTF8之間很容易互相轉換,計算量也不會太大。這里就不在把這些代碼貼出來了,有興趣的可以自己找一下,或者自己寫一個。

          但是,實際上Unicode和UTF8這兩個編碼格式基本是所有設備都會支持的,直接通過String類的編解碼方法互轉就可以了。

           4.6設備不支持的編碼格式

          如果需要使用設備不支持的編碼格式,那么必然將付出一些額外的代價。例如,如果設備不支持GB2312,則你可以有如下的幾個選項中選擇其一:

          1. 在你的程序中包含一個GB2312的字符對應表;(也許GB2312你還可以接收,但是GB18030要怎么辦呢?)
          2. 做一個代理服務器,將GB2312轉解碼成可以被設備支持的編碼;
          3. 放棄GB2312,選擇其他編碼;
          主站蜘蛛池模板: 祁门县| 五莲县| 和平县| 新宁县| 大宁县| 宿迁市| 密山市| 金溪县| 双鸭山市| 泗洪县| 西藏| 阳江市| 睢宁县| 永登县| 资兴市| 林西县| 固原市| 黄山市| 佛坪县| 屯留县| 仪陇县| 会宁县| 兴安盟| 依兰县| 剑河县| 两当县| 报价| 屏南县| 会同县| 金沙县| 马尔康县| 博客| 高台县| 明星| 锦屏县| 益阳市| 鹰潭市| 宁晋县| 怀柔区| 株洲市| 邻水|