要製作一個 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_脚本之家
0 回應:
張貼留言