早期 Java 用 16 位 char 表示 Unicode(BMP 範圍 0x0000~0xFFFF)。Unicode 3.1 擴展至 0x10FFFF,超出 16 位容量。UTF-16 引入代理對:高代理(0xD800~0xDBFF)+ 低代理(0xDC00~0xDFFF)組合表示補充字符(0x10000~0x10FFFF),共 1,048,576 碼位。Java 1.5 保留 char 相容性,新增碼位(code point,32 位)概念支援 UTF-32。
將字串轉為 int[] 碼位陣列。測試環境:10,000 代理對 + 10,000 非代理對,重複 10,000 次。
| 示例 | 方法 | 關鍵 API | 相對耗時 |
|---|---|---|---|
| 1-1 | 基準(忽略代理對) | charAt | 1.00x |
| 1-2 | 手動檢測 isSurrogatePair | isHighSurrogate, isLowSurrogate, toCodePoint | 1.38x |
| 1-3 | 標準 API | codePointCount, codePointAt, offsetByCodePoints | 2.80x |
| 1-4 | 反向遍歷 codePointBefore | codePointBefore, offsetByCodePoints | 2.72x |
| 1-5 | 優化:charCount | codePointAt, charCount | 1.68x |
| 1-6 | 直接存取 char[] | toCharArray, Character.codePointAt | 1.51x |
| 1-7 | 面向物件 CharBuffer | CharBuffer, IntBuffer, Character.codePointAt(CharSequence) | 2.15x |
// 1-5: 使用 charCount 避免 offsetByCodePoints 開銷
int[] toCodePointArray(String str) {
int[] acp = new int[str.codePointCount(0, str.length())];
for (int i = 0, cp; i < str.length(); i += Character.charCount(cp)) {
cp = str.codePointAt(i);
acp[j++] = cp;
}
return acp;
}
// 1-6: 大量資料時轉 char[] 更快
int[] toCodePointArray(String str) {
char[] ach = str.toCharArray();
int[] acp = new int[Character.codePointCount(ach, 0, ach.length)];
for (int i = 0, cp; i < ach.length; i += Character.charCount(cp)) {
cp = Character.codePointAt(ach, i);
acp[j++] = cp;
}
return acp;
}
按碼位索引切分字串。關鍵在於避免重複從頭計算 offsetByCodePoints:
// 低效:每次從 0 開始計算
int begin = str.offsetByCodePoints(0, pos);
int end = str.offsetByCodePoints(0, pos + width);
// 高效:從當前 char 位置偏移
int end = str.offsetByCodePoints(pos, width);
pos = end; // 直接更新 char 位置
測試顯示:優化版隨字串長度線性增長,未優化版呈平方級增長。
大多數 Character 方法提供 char 與 int (code point) 雙重載。關鍵差異在於補充字符與高代理的返回值不同(見原文表 2)。
Character.toChars(cp)、String(int[])、StringBuilder.appendCodePoint:碼位轉字串String.indexOf(cp)、StringBuffer.codePointCount:字串操作IllegalFormatCodePointException:格式化異常int cp = 0x20B9F;
new String(new int[]{cp}, 0, 1); // 206ms
new String(Character.toChars(cp)); // 187ms ✓
String.valueOf(Character.toChars(cp)); // 195ms
new StringBuilder().appendCodePoint(cp).toString(); // 269ms
String.format("%c", cp); // 3781ms ✗ (僅用於最終輸出)
Unicode 持續擴展(Emoji、古字符、東亞擴展),正確處理代理對是國際化軟體的必修課。掌握 codePointAt/charCount 組合與 offsetByCodePoints 的正確用法,能在相容性與效能間取得最佳平衡。
