要製作一個 Charset 一定要瞭解 Java 的字串核心,如果之前是寫 C 語言的朋友,首先要認知 char 跟 btye 是不一樣的,在 Java 的 char 是固定 Unicode 編碼,所有輸入都會轉成 Unicode,輸出時在轉成指定編碼,如下圖:
那麼要實做哪些東西呢??
- class Charset
- 定義 Charset 的名稱跟提供 Encoder 跟 Decoder,跟據 IANA 的規範自定的 Charset 名稱必須以
X-
或x-
開頭。 - class CharsetDecoder
- 字符解碼器負責將
byte[]
轉換成char[]
。 - class CharsetEncoder
- 字符編碼器負責將
char[]
轉換成byte[]
。 - class CharsetProvider
- Charset 提供者,以名稱提供 Charset,在
Charset.forName("xxx")
調用時會尋訪所有的 Provider 來取得 Charset。 - META-INF/services/java.nio.charset.spi.CharsetProvider
- 告知 JVM 將
CharsetProvider
註冊到延伸清單中,JVM 在啟動的時候會掃描所有這個路徑的檔案。
最後再將
{Charset}.jar
檔複製到 {jre}/lib/ext
目錄下就部署完畢Big5_Extend
public class Big5_Extend extends Charset { private static final String BASE_CHARSET = "Big5"; private static final String NAME = "X-Big5-Extend"; private static final String[] ALIASES = { "X-Big5_Extend" }; private Charset baseCharset; public Big5_Extend() { this(NAME, ALIASES); } public Big5_Extend(String canonical, String[] aliases) { super(canonical, aliases); baseCharset = Charset.forName(BASE_CHARSET); } public boolean contains(Charset cs) { return this.getClass().isInstance(cs) || baseCharset.getClass().isInstance(cs); } public CharsetDecoder newDecoder() { return new Decoder(this, baseCharset.newDecoder()); } public CharsetEncoder newEncoder() { return new Encoder(this, baseCharset.newEncoder()); } // ... }繼承自 Charset,我們只要實作名稱定義跟 Encoder / Decoder 兩件事,當然如果有 char mapping array 也是在這裡初始化,讓所有 Encoder 跟 Decoder 可以共用同一個記憶體空間。
解碼處理
protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) { base.reset(); /* 重置狀態 */ /* 先用原生的 Big5 進行解碼 */ CoderResult result = base.decode(in, out, true); if(!result.isUnmappable() || in.remaining() < 2){ return result; } /* 無法轉換,進一步使用自訂的解碼 */ int pos = in.position(); char big5Char = (char)(in.get(pos) << 8 | in.get(pos + 1)); char outChar; switch (big5Char) { case '\uFA40': outChar = '\u5803'; break; /* 堃 */ case '\uFA41': outChar = '\u83D3'; break; /* 菓 */ case '\uFA42': outChar = '\u854B'; break; /* 蕋 */ case '\uFA43': outChar = '\u4F8A'; break; /* 侊 */ default: return result; /* 不在清單內直接回傳 */ } out.put(outChar); in.position(pos + 2); return decodeLoop(in, out); /* 遞迴處理*/ }解碼的部分就是先呼叫 big5 原本的 decode,當發生無法解碼的四種狀況就會停止解碼,回傳解碼狀態,我們只要針對 isUnmappable 的狀態接著處裡,base.reset() 是為了清除 Decoder 內部的狀態紀錄,不然會被前一次的解碼結果所影響。
編碼處理
protected CoderResult encodeLoop(CharBuffer in, ByteBuffer out) { base.reset(); /* 重置狀態 */ /* 先用原生的 Big5 進行編碼 */ CoderResult result = base.encode(in, out, true); if(!result.isUnmappable() || out.remaining() < 2){ return result; } /* 無法轉換,進一步使用自訂的編碼 */ int pos = in.position(); char uniChar = in.get(pos); char outChar; switch (uniChar) { case '\u5803': outChar = '\uFA40'; break; /* 堃 */ case '\u83D3': outChar = '\uFA41'; break; /* 菓 */ case '\u854B': outChar = '\uFA42'; break; /* 蕋 */ case '\u4F8A': outChar ='\uFA43'; break; /* 侊 */ default: return result; /* 不在清單內直接回傳 */ } out.put((byte)(outChar >> 8)); out.put((byte)outChar); in.position(pos + 1); return encodeLoop(in, out); /* 遞迴處理*/ }編碼的部分跟解碼採用相同的方式,一樣是先呼叫 big5 原本的 encode。
CoderResult 四種狀態
- UNDERFLOW 欠位
- OVERFLOW 溢位
- MALFORMED 有缺陷的輸入
- UNMAPPABLE 無映射字符
完整的 Big5_Extend
package com.custom.nio.charset; import java.nio.CharBuffer; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; import java.nio.charset.CharsetDecoder; import java.nio.charset.CoderResult; public class Big5_Extend extends Charset { private static final String BASE_CHARSET = "Big5"; private static final String NAME = "X-Big5-Extend"; private static final String[] ALIASES = { "X-Big5_Extend" }; private Charset baseCharset; public Big5_Extend() { this(NAME, ALIASES); } public Big5_Extend(String canonical, String[] aliases) { super(canonical, aliases); baseCharset = Charset.forName(BASE_CHARSET); } public boolean contains(Charset cs) { return this.getClass().isInstance(cs) || baseCharset.getClass().isInstance(cs); } public CharsetDecoder newDecoder() { return new Decoder(this, baseCharset.newDecoder()); } public CharsetEncoder newEncoder() { return new Encoder(this, baseCharset.newEncoder()); } private class Decoder extends CharsetDecoder { /* Java 原生的 Big5 解碼器 */ private final CharsetDecoder base; Decoder(Charset cs, CharsetDecoder base) { super(cs, base.averageCharsPerByte(), base.maxCharsPerByte()); this.base = base; } @Override protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) { base.reset(); /* 重置狀態 */ /* 先用原生的 Big5 進行解碼 */ CoderResult result = base.decode(in, out, true); if(!result.isUnmappable() || in.remaining() < 2){ return result; } /* 無法轉換,進一步使用自訂的解碼 */ int pos = in.position(); char big5Char = (char)(in.get(pos) << 8 | in.get(pos + 1)); char outChar; switch (big5Char) { case '\uFA40': outChar = '\u5803'; break; /* 堃 */ case '\uFA41': outChar = '\u83D3'; break; /* 菓 */ case '\uFA42': outChar = '\u854B'; break; /* 蕋 */ case '\uFA43': outChar = '\u4F8A'; break; /* 侊 */ default: return result; /* 不在清單內直接回傳 */ } out.put(outChar); in.position(pos + 2); return decodeLoop(in, out); } } private class Encoder extends CharsetEncoder { /* Java 原生的 Big5 編碼器 */ private final CharsetEncoder base; Encoder(Charset cs, CharsetEncoder base) { super(cs, base.averageBytesPerChar(), base.maxBytesPerChar()); this.base = base; } @Override protected CoderResult encodeLoop(CharBuffer in, ByteBuffer out) { base.reset(); /* 重置狀態 */ /* 先用原生的 Big5 進行編碼 */ CoderResult result = base.encode(in, out, true); if(!result.isUnmappable() || out.remaining() < 2){ return result; } /* 無法轉換,進一步使用自訂的編碼 */ int pos = in.position(); char uniChar = in.get(pos); char outChar; switch (uniChar) { case '\u5803': outChar = '\uFA40'; break; /* 堃 */ case '\u83D3': outChar = '\uFA41'; break; /* 菓 */ case '\u854B': outChar = '\uFA42'; break; /* 蕋 */ case '\u4F8A': outChar ='\uFA43'; break; /* 侊 */ default: return result; /* 不在清單內直接回傳 */ } out.put((byte)(outChar >> 8)); out.put((byte)outChar); in.position(pos + 1); return encodeLoop(in, out); } } }
CharsetProvider
package com.custom.nio.charset; import java.nio.charset.Charset; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; /** 字元編碼器連結器,用來向 JVM 提交自訂的編碼器 */ public class CharsetProvider extends java.nio.charset.spi.CharsetProvider { static Map<String, Charset> name2charset; static Collection<Charset> charsets; public Charset charsetForName(String charsetName) { if (charsets == null){ init(); } return name2charset.get(charsetName.toLowerCase()); } public Iterator<Charset> charsets() { if (charsets == null){ init(); } return charsets.iterator(); } void init() { name2charset = new HashMap<String, Charset>(); charsets = new HashSet<Charset>(); charsets.add(new Big5_Extend()); for (Charset charset : charsets) { name2charset.put(charset.name().toLowerCase(), charset); for (String aliase: charset.aliases()) { name2charset.put(aliase.toLowerCase(), charset); } } } }
java.nio.charset.spi.CharsetProvider
com.custom.nio.charset.CharsetProvider內容就一行類別定義
測試
public class Test { public static void main(String[] args) throws Throwable { String charset = "X-Big5-Extend"; String source = "堃菓蕋侊"; byte[] bytes = source.getBytes(charset); for (byte b : bytes) { System.out.printf("%x ", b); } System.out.println("\n"); // fa 40 fa 41 fa 42 fa 43 String result = new String(bytes, charset); System.out.println(result); // 堃菓蕋侊 } }
參考自:Java字符编码解码的实现详解_java_脚本之家
沒有留言:
張貼留言
你好!歡迎你在我的 Blog 上留下你寶貴的意見。