MIDP 標準持久化方案 RMS 以 RecordStore 為表,RecordID (int) 為主鍵,byte[] 為內容。**缺陷**:只能按 int ID 精確查找或全表掃描 byte[],無法用業務鍵 (如 "user.info", "settings") 直接訪問。本文設計 StorageHandler 框架,引入索引表實現 String 鍵值查找,配合 Storable 接口實現異構對象序列化。
+------------------+ +------------------+ +------------------+
| 業務層 | | StorageHandler | | RMS 底層 |
| (UserInfo, |---->| (單例門面) |---->| (RecordStore) |
| Settings, ...) | | | | |
| 實現 Storable | | - indexStore | | - indexStore |
| | | - dataStore(s) | | - dataStore(s) |
+------------------+ +------------------+ +------------------+
^ ^
| getKey() | IndexEntry 映射
| getData()/setData() | String Key ↔ RecordID
class IndexEntry {
private int selfId; // 索引記錄自身 ID
private int recordId; // 指向數據記錄的 ID
private String key; // 業務鍵 (如 "user.info")
// 序列化/反序列化方法省略
}
indexStore 中按 key 查找 IndexEntry (啟動時載入 Vector 內存索引,O(1) 查找)recordIdrecordId 從對應 dataStore 讀取 byte[],反序列化為業務對象public interface Storable {
String getKey(); // 業務唯一鍵
void getData(DataOutputStream out) throws IOException; // 序列化
void setData(DataInputStream in) throws IOException; // 反序列化
}
採用 DataOutputStream/DataInputStream 而非 Java 序列化:體積小、速度快、無 serialVersionUID 煩惱、跨語言友好。
public class UserInfo implements Storable {
private String username;
private String password;
private boolean autoLogin;
@Override
public String getKey() { return "user.info"; }
@Override
public void getData(DataOutputStream out) throws IOException {
out.writeUTF(username);
out.writeUTF(password);
out.writeBoolean(autoLogin);
}
@Override
public void setData(DataInputStream in) throws IOException {
username = in.readUTF();
password = in.readUTF();
autoLogin = in.readBoolean();
}
// getter/setter 省略
}
public class StorageHandler {
private static StorageHandler instance = new StorageHandler();
private RecordStore indexStore;
private RecordStore dataStore;
private Vector indexCache; // 內存索引
private StorageHandler() {
try {
indexStore = RecordStore.openRecordStore("index", true);
dataStore = RecordStore.openRecordStore("data", true);
loadIndexCache(); // 啟動時全量載入
} catch (RecordStoreException e) { /* 致命錯誤處理 */ }
}
public static StorageHandler getInstance() { return instance; }
// 保存/更新
public void save(Storable obj) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
obj.getData(dos);
dos.flush();
byte[] data = baos.toByteArray();
String key = obj.getKey();
IndexEntry entry = findIndexEntry(key);
if (entry == null) {
// 新增
int recordId = dataStore.addRecord(data, 0, data.length);
entry = new IndexEntry();
entry.key = key;
entry.recordId = recordId;
addIndexEntry(entry);
} else {
// 更新
dataStore.setRecord(entry.recordId, data, 0, data.length);
}
syncIndexCache(); // 持久化索引變更
} catch (Exception e) { /* 異常處理 */ }
}
// 讀取
public void load(Storable obj) {
try {
IndexEntry entry = findIndexEntry(obj.getKey());
if (entry != null) {
byte[] data = dataStore.getRecord(entry.recordId);
ByteArrayInputStream bais = new ByteArrayInputStream(data);
DataInputStream dis = new DataInputStream(bais);
obj.setData(dis);
}
} catch (Exception e) { /* 異常處理 */ }
}
// 刪除
public void delete(String key) { /* 刪除數據記錄 + 索引記錄 + 更新緩存 */ }
// 內存索引操作 (O(1) 查找)
private IndexEntry findIndexEntry(String key) {
for (IndexEntry e : indexCache) if (e.key.equals(key)) return e;
return null;
}
// loadIndexCache, addIndexEntry, syncIndexCache 等輔助方法省略
}
| 決策點 | 方案 | 理由 |
|---|---|---|
| 索引存儲 | 獨立 RecordStore ("index") | 職責分離,索引小可全量緩存 |
| 內存索引結構 | Vector | 索引量通常 < 100,線性足夠;若大量可換 HashMap |
| 序列化格式 | DataOutputStream (UTF/Boolean/Int) | J2ME 標準、無反射、字節序固定、體積最小 |
| 異構對象支持 | Storable 接口 + 不同 RecordStore | 每類對象可獨立數據存儲,互不干擾 |
| 併發安全 | 單線程 MIDlet 模型天然安全 | 若多線程需加 synchronized |