本文先容JavaME中文編碼的相干標題,這個標題一度是互聯網上的開發者們討論的熱點話題。本文收拾和綜合了網上眾多相干內容,盡可能的為開發者供給一個全面、系統的熟悉。
文中的代碼僅用來闡明原理,可能很不完整,缺乏變量定義或者返回值,請體諒。部分代碼直接起源於網上的其他材料。
感謝眾多開發者在中文編碼標題上做出的努力與摸索。總結中有什麼標題的話,歡迎大家指正:)
基於羅馬字母表的一套電子盤算器編碼系統,是單字節的編碼方法,每個ASCII字符占用1個字節(8bits),所以ASCII編碼最多可以表現256個字符。它是美國信息交換尺度委員會(American Standards Committee for InformationInterchange)的縮寫, 為美國英語通信所設計。
顯然ASCII編碼用來表現英文字母和字符是足夠了的,但是對於中文和日文等眾多的文字來說,是遠遠不夠的。
跟ASCII類似的編碼還有ISO8859-1。
雙字節編碼方法,它為每種語言中的每個字符設定了同一並且唯一的二進制編碼,以滿足跨語言、跨平台進行文本基准轉換、處理的請求。UNICODE支撐歐洲、非洲、中東、亞洲(包含同一尺度的東亞像形漢字和韓國像形文字)的文字。
UNICODE又可以分為“高位在前”和“低位在前”的兩種格局,這和CPU的處理方法有點關系。這一點可以通過BOM(Byte OrderMark)來標示,若采用 “低位在前”方法編碼,BOM 會表現為 0xFF 0xFE,而在 Unicode 的定義中是不存在 U+FFFE這個字符的。若采用高位在前方法編碼,BOM 會表現為 0xFE 0xFF,而 U+FEFF 恰好是在 Unicode中的有效字符,代表的是一個不占空間的 space 符號,所以即使沒被說明為 BOM,也不會對閱覽者產生錯誤的信息。
但UINICODE也帶來一些標題,當美國人看見自己天天最常用的字符需要用兩倍的空間來保留時,自然會感到這是一種浪費,他們必定會說:看看那堆0。於是新的編碼方法又出生了。
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: 0******x U-00000080 - U-000007FF: 110***xx 10****** U-00000800 - U-0000FFFF: 1110***x 10****** 10****** U-00010000 - U-001FFFFF: 11110*** 10****** 10****** 10****** U-00200000 - U-03FFFFFF: 111110xx 10****** 10****** 10****** 10****** U-04000000 - U-7FFFFFFF: 1111110x 10****** 10****** 10****** 10****** 10******
上表中的***為Unicode編碼的二進制數據。例如:“中”字,Unicode編碼為4E2D。
Unicode: 4E 2D 01001110 00101101UTF-8: E4 B8 AD 11100100 10111000 10101101
對於英文來說,UTF-8跟ISO8859-1一樣節儉;但顯然中文等字符將為UTF-8付出更多。
GB2312又稱國標碼,由國家尺度總局宣布,1981年5月1日實行,通行於大陸。新加坡等地也應用此編碼。它是一個簡化字的編碼規范,當然也包含其他的符號、字母、日文假名等,共7445個圖形字符,其中漢字占6763個。我們平時說6768個漢字,實際上裡邊有5個編碼為空缺,所以總共有6763個漢字。
GB2312規定“對任意一個圖形字符都采用兩個字節表現,每個字節均采用七位編碼表現”,習慣上稱第一個字節為“高字節”,第二個字節為“低字節”。GB2312中漢字的編碼范疇為,第一字節0xB0-0xF7(對應十進制為176-247),第二個字節0xA0-0xFE(對應十進制為160-254)。
GB2312具有許多擴大,其中GBK是微軟對GB2312的擴大,GB18030則是2000年宣布的國家尺度,是到目前為止最新的國標漢字編碼。
說到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,
一個最直接的獲取編碼支撐的方法是應用System.getProperty(“microedition.encoding”),可以得到設備的默認的字符編碼,以NOKIA設備為例,得到的屬性值為ISO8859-1。
然而,通過這個方法的意義並不大。首先,它只能獲取到一個編碼格局,而一般設備都會支撐許多種編碼規范;其次,這個屬性的數值與虛擬機的實現有很大關系,同樣以NOKIAS40v2為例,不論設備的目標市場應用什麼語言,這個屬性同一為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的規范中並沒有給出嚴格的定義。所以在實際測試的過程中需要充分考慮到這個情況。
CLDC中還有兩個方法跟字符編碼有關系:DataInputStream中的readUTF()和DataOutputStream中的writeUTF(String)。根據兩個方法的JavaDoc,writeUTF(String)首先會向輸出流中寫進字符串編碼成UTF8格局後的byte數組長度(2個字節),然後再將這個UTF8的byte數組寫進。而readUTF()則是先從輸進流中讀取2個字節,組成一個short數值,在從輸進流中讀出這個數值長度的byte數據,再將這個byte數組解碼成字符串。具體闡明請參考DataInputStream和DataOutputStream的Java Document。
上一部分中先容了CLDC平台上所有跟字符編解碼相干的API。懂得了這些內容以後,就可以聯合具體的情況來考慮如何解決中文編碼的標題了。
首先,中文編碼標題中最常見的情況就是亂碼,那麼亂碼是如何產生的呢?無非是以下幾種情況:
所以,解決中文編碼標題的幾個基礎思路是:
這個標題完整可以應用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數據,可以有許多方法,可以應用一些UltralEditor之類的文本工具,或應用一些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,則你可以有如下的幾個選項中選擇其一:
在你的程序中包含一個GB2312的字符對應表;(也許GB2312你還可以接收,但是GB18030要怎麼辦呢?) 做一個代理服務器,將GB2312轉解碼成可以被設備支撐的編碼; 放棄GB2312,選擇其他編碼;