首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

J2ME 字串資源管理與多語言支援:基於 Hashtable 的輕量級國際化框架

     編輯:Java 相關知識

設計目標

  • **資源化管理**:字串外部化至資源檔,代碼與內容分離
  • **靜態存取**:`StringManager.get("Key")` 一行代碼取得字串
  • **多語言切換**:運行時切換語言包 (zh_CN, en_US, ja_JP...)
  • **鍵值命名規範**:`類別名.內部名稱` (如 `GameCanvas.StartButton`)
  • **J2ME 輕量化**:無 XML 解析器,純文本格式,最小依賴

資源檔格式設計

**格式**:`鍵值 分隔符 值`,換行分隔條目

# /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
  • 分隔符:空格或 Tab (`\t`),支援值包含空格
  • 編碼:**UTF-8** (J2ME 預設支援)
  • 註解:`#` 開頭行忽略
  • 放置位置:`/res/strings/{基礎名}.{語言代碼}` (如 `GameCanvas.zh_CN`)

核心實作:StringManager

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;
    }
}

使用範例

初始化 (MIDlet 啟動時)

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 記憶體佔用較大 (可考慮分模組載入)

進階擴展方向

1. 多資源檔並行支援

// 以資源基礎名為鍵管理多個 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")

2. 參數化格式化 (輕量實現)

// 資源檔: 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;
}

3. 記憶體優化:分模組載入/卸載

public void unload(String baseName) {
    resourceMap.remove(baseName);
    System.gc();  // 建議釋放
}

// 進入關卡載入關卡字串,離開關卡卸載
stringManager.load("Level1");
...
stringManager.unload("Level1");

4. 編譯期鍵值檢查 (註解處理器/外部工具)

  • 掃描 Java 代碼中 `StringManager.get("...")` 參數
  • 對照資源檔鍵值,**編譯期發現拼寫錯誤、缺漏翻譯**

總結

J2ME 環境下,**`StringManager` 以極低成本 (單檔、<2KB 代碼、零依賴) 解決了字串硬編碼、多語言切換、資源管理三大痛點**。核心設計決策:

  1. **純文本資源** 適應 J2ME 無 XML 解析器現實
  2. **Hashtable** 提供 O(1) 查找,滿足遊戲高頻 UI 繪製需求
  3. **單例 + 靜態 get()** 讓調用點極簡潔 (`StringManager.get("Key")`)
  4. **鍵值命名規範** (`類別.欄位`) 從架構層面避免衝突
  5. **缺鍵回傳鍵** 讓缺漏翻譯在測試階段一眼可見

這個微型框架展示了**在極度資源受限環境下,如何用最小代價實現最大價值**的工程智慧——同樣適用於現代嵌入式、IoT、輕量級微服務等場景。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved