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

J2ME RMS 核心技術深度解析:從記錄結構到 RecordStore 完整封裝

     編輯:J2ME

RMS (Record Management System) 是 J2ME 唯一的持久化儲存機制,對遊戲存檔、高分榜、設定保存至關重要。本文提供生產級的兩層封裝架構:記錄結構類 (資料模型) + RMS 管理類 (存取邏輯),含完整異常處理與索引查詢功能。

架構設計


Appointment (記錄結構)  ←→  byte[]  ←→  RecordStore
     ↑                              ↑
toBytes()/init()              RMS 管理類

第一層:記錄結構類 Appointment


package com.cuilichen.usual;

import java.io.*;

public class Appointment {
    // 業務欄位
    private int int1, int2;
    private long long1;
    private String str1, str2, str3;  // str1 作為檢索鍵值
    private boolean wroteFlag;

    // 構造函數:新建記錄
    public Appointment(int _int1, int _int2, long _long1,
                       String _str1, String _str2, String _str3, boolean _flag) {
        this.int1 = _int1; this.int2 = _int2; this.long1 = _long1;
        this.str1 = _str1; this.str2 = _str2; this.str3 = _str3;
        this.wroteFlag = _flag;
    }

    // 構造函數:從 RMS 位元組陣列還原
    public Appointment(byte[] rec) { initFromBytes(rec); }

    // 序列化為位元組陣列 (寫入 RMS)
    public byte[] toBytes() {
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            DataOutputStream dos = new DataOutputStream(baos);
            dos.writeInt(int1);
            dos.writeInt(int2);
            dos.writeLong(long1);
            dos.writeUTF(str1);
            dos.writeUTF(str2);
            dos.writeUTF(str3);
            dos.writeBoolean(wroteFlag);
            dos.flush();
            byte[] data = baos.toByteArray();
            dos.close(); baos.close();
            return data;
        } catch (Exception e) { e.printStackTrace(); return null; }
    }

    // 反序列化 (讀取 RMS)
    private void initFromBytes(byte[] rec) {
        try {
            ByteArrayInputStream bais = new ByteArrayInputStream(rec);
            DataInputStream dis = new DataInputStream(bais);
            int1 = dis.readInt();
            int2 = dis.readInt();
            long1 = dis.readLong();
            str1 = dis.readUTF();
            str2 = dis.readUTF();
            str3 = dis.readUTF();
            wroteFlag = dis.readBoolean();
            dis.close(); bais.close();
        } catch (Exception e) { e.printStackTrace(); }
    }

    // Getter 方法...
    public int getInt1() { return int1; }
    public String getStr1() { return str1; }  // 檢索關鍵
    // ... 其他 getter 略
}

設計要點

  • DataOutputStream/DataInputStream:保證跨平台位元組序列化一致性
  • writeUTF/readUTF:正確處理 Unicode 字串長度前綴
  • 欄位順序固定:寫入/讀出順序必須完全一致
  • str1 為檢索鍵:設計時指定一欄位作為查詢索引(如 ID、名稱)

第二層:RMS 管理類 RMS


package com.cuilichen.usual;

import javax.microedition.rms.*;

public class RMS {
    // 預設值常數
    public static final int DEF_INT = 0;
    public static final long DEF_LONG = 0L;
    public static final String DEF_STR = "";

    // 新增記錄
    public static boolean addRecord(String storeName, int int1, int int2, long long1,
                                    String str1, String str2, String str3, boolean flag) {
        try {
            RecordStore rs = RecordStore.openRecordStore(storeName, true);
            Appointment appt = new Appointment(int1, int2, long1, str1, str2, str3, flag);
            rs.addRecord(appt.toBytes(), 0, appt.toBytes().length);
            rs.closeRecordStore();
            return true;
        } catch (Exception e) { e.printStackTrace(); return false; }
    }

    // 取得記錄總數
    public static int getRecordCount(String storeName) {
        try {
            RecordStore rs = RecordStore.openRecordStore(storeName, true);
            int count = rs.getNumRecords();
            rs.closeRecordStore();
            return count;
        } catch (Exception e) { return 0; }
    }

    // 取得所有記錄 (陣列)
    public static Appointment[] getAllRecords(String storeName) {
        try {
            RecordStore rs = RecordStore.openRecordStore(storeName, false);
            RecordEnumeration re = rs.enumerateRecords(null, null, false);
            Appointment[] result = new Appointment[rs.getNumRecords()];
            for (int i = 0; i < result.length; i++) {
                int recId = re.nextRecordId();
                result[i] = new Appointment(rs.getRecord(recId));
            }
            rs.closeRecordStore();
            return result;
        } catch (Exception e) { return new Appointment[0]; }
    }

    // 依記錄 ID 取得單筆
    public static Appointment getRecordById(String storeName, int recordId) {
        try {
            RecordStore rs = RecordStore.openRecordStore(storeName, false);
            Appointment appt = new Appointment(rs.getRecord(recordId));
            rs.closeRecordStore();
            return appt;
        } catch (Exception e) { return new Appointment(); }
    }

    // 依檢索鍵 (str1) 查詢記錄 ID
    public static int findRecordIdByKey(String storeName, String key) {
        try {
            RecordStore rs = RecordStore.openRecordStore(storeName, false);
            RecordEnumeration re = rs.enumerateRecords(null, null, false);
            int count = getRecordCount(storeName);
            for (int i = 0; i < count; i++) {
                int recId = re.nextRecordId();
                Appointment appt = new Appointment(rs.getRecord(recId));
                if (appt.getStr1().equals(key)) {
                    rs.closeRecordStore();
                    return recId;
                }
            }
            rs.closeRecordStore();
        } catch (Exception e) {}
        return -1;  // 未找到
    }

    // 更新指定 ID 記錄
    public static boolean updateRecord(String storeName, int recordId,
                                       int int1, int int2, long long1,
                                       String str1, String str2, String str3, boolean flag) {
        try {
            RecordStore rs = RecordStore.openRecordStore(storeName, false);
            Appointment appt = new Appointment(int1, int2, long1, str1, str2, str3, flag);
            rs.setRecord(recordId, appt.toBytes(), 0, appt.toBytes().length);
            rs.closeRecordStore();
            return true;
        } catch (Exception e) { return false; }
    }

    // 刪除記錄 (可擴充)
    public static boolean deleteRecord(String storeName, int recordId) {
        try {
            RecordStore rs = RecordStore.openRecordStore(storeName, false);
            rs.deleteRecord(recordId);
            rs.closeRecordStore();
            return true;
        } catch (Exception e) { return false; }
    }
}

實戰使用範例


// MIDlet 啟動時初始化測試資料
private static final String STORE_NAME = "GameData";

protected void startApp() {
    if (RMS.getRecordCount(STORE_NAME) == 0) {
        for (int i = 0; i < 6; i++) {
            // 只使用 int2、str1(鍵)、str3 三個欄位
            RMS.addRecord(STORE_NAME,
                RMS.DEF_INT, i, RMS.DEF_LONG,
                String.valueOf(i),  // str1 = "0","1","2"...
                RMS.DEF_STR,
                "1234567890123456789",
                false);
        }
    }
    // 後續查詢範例
    int id = RMS.findRecordIdByKey(STORE_NAME, "3");
    if (id != -1) {
        Appointment appt = RMS.getRecordById(STORE_NAME, id);
        // appt.getStr3() → "1234567890123456789"
    }
}

進階議題與最佳實務

1. 異常處理策略

範例為簡潔在內部捕獲並列印,生產環境建議:

  • 自訂 RMSException 封裝底層例外向上拋出
  • 區分 RecordStoreFullException (空間不足)、RecordStoreNotFoundException (存儲不存在) 等具體錯誤
  • 提供錯誤碼/訊息供 UI 顯示或上報

2. 效能優化

  • RecordEnumeration 重用:避免重複開啟/關閉 RecordStore
  • 批次操作:大量寫入時一次開啟、循環 addRecord、最後關閉
  • 索引快取:啟動時將 str1→recordId 映射載入記憶體 Hashtable,查詢 O(1)

3. 版本遷移

記錄結構變更時:

  1. 在 Appointment 加入 version 欄位
  2. initFromBytes 依版本號兼容舊格式讀取
  3. 提供遷移工具將舊記錄轉換新格式

4. 安全性考量

  • 敏感資料 (密碼、金鑰) 加密後再存入 RMS
  • 利用 MIDlet Suite 隔離:同套件 MIDlet 可共享 RecordStore (openRecordStore(name, true, RecordStore.AUTHMODE_ANY, true))

總結

這套「記錄類 + 管理類」雙層架構,將資料模型與存取邏輯分離,符合單一職責原則,且易於單元測試與維護。掌握 DataOutputStream 序列化、RecordEnumeration 遍歷、索引查詢模式,即能驾輕就熟處理 J2ME 所有持久化需求。

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