**格式**:`鍵值 分隔符 值`,換行分隔條目
# /res/strings/GameCanvas.zh_CN
GameCanvas.StartButton 開始遊戲
GameCanvas.ExitButton 退出
GameCanvas.ScoreLabel 分數:
GameCanvas.HighScore 最高分
# /res/strings/GameCanvas.en_US
GameCanvas.StartButton Start Game
GameCanvas.ExitButton Exit
GameCanvas.ScoreLabel Score:
GameCanvas.HighScore High Score
package vmlinux.app;
import java.io.*;
import java.util.*;
public class StringManager {
private String lang;
private String resourceBaseName;
private Hashtable strings;
private boolean loaded;
private static StringManager instance;
private StringManager(String lang) {
this.lang = lang;
this.loaded = false;
}
/** 取得單例實例 (雙重檢查鎖定模式簡化版,J2ME 單執行緒環境足夠) */
public static StringManager getInstance(String lang) {
if (instance == null) {
instance = new StringManager(lang);
} else {
instance.setLang(lang);
}
return instance;
}
/** 靜態便捷方法:直接取字串 */
public static String get(String key) {
if (instance == null) return key; // 未初始化回傳鍵值本身 (除錯可見)
return instance.getString(key);
}
/** 切換語言 (自動標記需重載) */
public void setLang(String lang) {
if (this.lang.equals(lang)) return;
this.lang = lang;
this.loaded = false;
}
/** 載入指定資源檔 */
public void load(String baseName) {
if (this.resourceBaseName != null &&
this.resourceBaseName.equals(baseName) &&
this.loaded) return;
if (strings == null) strings = new Hashtable();
else strings.clear();
this.resourceBaseName = baseName;
// 從 JAR 內部資源載入: /strings/{baseName}.{lang}
String resourcePath = "/strings/" + baseName + "." + lang;
InputStream is = getClass().getResourceAsStream(resourcePath);
if (is == null) {
// 資源不存在:可選拋異常、回退預設語言、或保持空表
System.err.println("String resource not found: " + resourcePath);
this.loaded = true; // 避免重複嘗試
return;
}
try {
byte[] buf = new byte[1024];
int count;
StringBuffer tokenBuf = new StringBuffer();
String key = null;
int state = 0; // 0=讀鍵, 1=讀值
while ((count = is.read(buf)) > 0) {
String chunk = new String(buf, 0, count);
for (int i = 0; i < chunk.length(); i++) {
char c = chunk.charAt(i);
if (state == 0) { // 讀取鍵
if (c == ' ' || c == '\t') {
key = tokenBuf.toString();
tokenBuf.setLength(0);
state = 1;
} else if (c == '\n' || c == '\r') {
// 空行或格式錯誤,重置
tokenBuf.setLength(0);
key = null;
} else {
tokenBuf.append(c);
}
} else { // state == 1, 讀取值
if (c == '\n') {
if (key != null && key.length() > 0) {
strings.put(key, tokenBuf.toString());
}
key = null;
tokenBuf.setLength(0);
state = 0;
} else {
tokenBuf.append(c);
}
}
}
}
// 處理最後一行無換行結尾情況
if (key != null && key.length() > 0) {
strings.put(key, tokenBuf.toString());
}
is.close();
this.loaded = true;
} catch (IOException e) {
System.err.println("Load string resource failed: " + e.getMessage());
this.loaded = true; // 避免無限重試
}
}
/** 取得字串,找不到回傳鍵值本身 (便於除錯識別缺漏) */
public String getString(String key) {
Object val = strings.get(key);
return (val != null) ? (String) val : key;
}
}
public class MyMIDlet extends MIDlet {
protected void startApp() {
// 初始化字串管理器,載入預設語言資源
StringManager.getInstance("zh_CN").load("GameCanvas");
// 進入主畫面...
}
}
public class GameCanvas extends Canvas {
protected void paint(Graphics g) {
// 靜態方法一行取字串
g.drawString(StringManager.get("GameCanvas.Title"), 0, 0, Graphics.TOP | Graphics.LEFT);
g.drawString(StringManager.get("GameCanvas.ScoreLabel") + score, 0, 20, Graphics.TOP | Graphics.LEFT);
}
protected void keyPressed(int keyCode) {
if (keyCode == Canvas.KEY_SOFT1) {
// 切換語言示例
StringManager.getInstance("en_US").load("GameCanvas");
repaint();
}
}
}
| 優點 | 缺點 |
|---|---|
| 零依賴,純 J2ME 標準 API | **同一時間僅支援一個資源檔** (單例模式限制) |
| 純文本資源,易編輯、版控、翻譯流程 | 無參數化格式化支援 (如 `String.format("{0} 得分 {1}", name, score)`) |
| Hashtable O(1) 查找,效能極佳 | 無複數形式支援 (英文單複數、斯拉夫語複數規則) |
| 鍵值命名規範避免衝突,模組化清晰 | 運行時切換語言需手動 `load()`,非自動監聽 |
| 找不到鍵回傳鍵值本身,除錯極易發現缺漏 | 大量字串時 Hashtable 記憶體佔用較大 (可考慮分模組載入) |
// 以資源基礎名為鍵管理多個 Hashtable
private Hashtable resourceMap = new Hashtable(); // baseName -> Hashtable
public void load(String baseName) { ... } // 載入至 resourceMap.get(baseName)
public String getString(String baseName, String key) {
Hashtable table = (Hashtable) resourceMap.get(baseName);
return (table != null) ? (String) table.get(key) : key;
}
// 使用: StringManager.get("GameCanvas", "StartButton")
// 資源檔: GameCanvas.MsgWelcome 歡迎 {0},你的等級是 {1}
// 代碼:
public static String format(String key, Object... args) {
String template = get(key);
for (int i = 0; i < args.length; i++) {
template = replace(template, "{" + i + "}", String.valueOf(args[i]));
}
return template;
}
public void unload(String baseName) {
resourceMap.remove(baseName);
System.gc(); // 建議釋放
}
// 進入關卡載入關卡字串,離開關卡卸載
stringManager.load("Level1");
...
stringManager.unload("Level1");
J2ME 環境下,**`StringManager` 以極低成本 (單檔、<2KB 代碼、零依賴) 解決了字串硬編碼、多語言切換、資源管理三大痛點**。核心設計決策:
這個微型框架展示了**在極度資源受限環境下,如何用最小代價實現最大價值**的工程智慧——同樣適用於現代嵌入式、IoT、輕量級微服務等場景。