Java ME 無反射、無 Serializable、無自動序列化,直接操作 byte[] 極易出錯。本文提出一套輕量持久化框架,四大核心介面/類:
- Serializable:定義
serialize()/unserialize()/getId()/setId(),類似 Java SE 標記介面但含契約方法。
- Lazy extends Serializable:大物件分片存儲支援,
getAttach(key)/savedAttach(key,id)/getAttachKeys()/getNameOfAttachRMS()。
- RecordFetcher extends RecordFilter, RecordComparator:查詢參數封裝,含分頁
getFromIndex()/getMaxRecordSize()。
- RMSUtil:單例緩存
RecordStore,提供 insert/update/delete/read/list 全套靜態方法,自動處理 Lazy 附件分庫存儲。
核心介面定義
Serializable
public interface Serializable {
byte[] serialize() throws IOException;
void unSerialize(byte[] data) throws IOException;
void setId(int id);
int getId(); // 未持久化時回傳 -1
}
Lazy(大物件分片)
public interface Lazy extends Serializable {
byte[] getAttach(Object key) throws IOException; // 取得分片字節陣列
void savedAttach(Object key, int id); // 儲存後回調,記錄分片 RMS ID
Object[] getAttachKeys(); // 所有分片鍵
String getNameOfAttachRMS(); // 分片存儲的 RecordStore 名稱
}
RecordFetcher(查詢封裝)
public interface RecordFetcher extends RecordFilter, RecordComparator {
int getFromIndex(); // 起始索引(分頁)
int getMaxRecordSize(); // 最大回傳條數
}
RMSUtil 核心功能
| 方法 | 說明 |
insertObject(Serializable) | 插入物件,自動處理 Lazy 附件寫入 |
updateObject(Serializable) | 更新主記錄 |
deleteObject(Serializable) | 刪除主記錄與附件 |
readObject(id, Class) | 反序列化單個物件,失敗自動清理髒數據 |
listObjects(Class, from, maxSize) | 分頁列舉,可選 RecordFetcher 過濾/排序 |
getStoreSize(Class) | 該類記錄總數 |
closeAllRMS() | MIDlet 銷毀時調用,關閉所有緩存的 RecordStore |
關鍵實現細節
RecordStore 緩存(避免重複開啟開銷)
private static Hashtable rmsCache = new Hashtable();
private static synchronized RecordStore getRecordStore(String name) throws RecordStoreException {
if (name.length() > 32) name = name.substring(name.length() - 32); // RMS 名稱長度限制
if (rmsCache.containsKey(name)) return (RecordStore) rmsCache.get(name);
RecordStore rs = RecordStore.openRecordStore(name, true);
rmsCache.put(name, rs);
return rs;
}
Lazy 附件自動分庫存儲
private static boolean insertAttachDatas(Lazy lazy) {
RecordStore rs = getRecordStore(lazy.getNameOfAttachRMS());
Object[] keys = lazy.getAttachKeys();
for (Object key : keys) {
byte[] data = lazy.getAttach(key);
int id = rs.addRecord(data, 0, data.length);
lazy.savedAttach(key, id); // 回調物件記錄分片 ID
}
return true;
}
讀取時自動清理髒數據
public static Serializable readObject(int id, Class clz) {
try {
RecordStore rs = getRecordStore(clz.getName());
byte[] data = rs.getRecord(id);
Serializable ser = (Serializable) clz.newInstance();
ser.unSerialize(data);
ser.setId(id);
return ser;
} catch (Exception e) {
// 反序列化失敗(版本不兼容/數據損壞)→ 刪除該記錄防止下次再失敗
if (rs != null) { try { rs.deleteRecord(id); } catch (Exception ignored) {} }
return null;
}
}
使用範例
// 1. 定義實體實現 Serializable
public class User implements Serializable {
private int id = -1;
private String name;
private int level;
public byte[] serialize() {
// 手動序列化:name 長度(2B) + name bytes + level(4B)
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
dos.writeUTF(name);
dos.writeInt(level);
return baos.toByteArray();
}
public void unSerialize(byte[] data) {
DataInputStream dis = new DataInputStream(new ByteArrayInputStream(data));
name = dis.readUTF();
level = dis.readInt();
}
// getId/setId ...
}
// 2. 業務代碼
User u = new User(); u.name = "Hero"; u.level = 99;
RMSUtil.insertObject(u); // 自動分配 ID 並寫入
// ...
User loaded = (User) RMSUtil.readObject(u.getId(), User.class);
設計權衡與建議
- 優點:零反射、零註解、編譯期類型安全、分片支援大物件、查詢介面靈活。
- 缺點:需手寫序列化代碼(但比動態字節碼注入更可控、無方法名衝突風險)。
- 適用場景:中大型專案、需跨版本升級、大物件(圖片、地圖)分片存儲。
- 擴展建議:加入版本號欄位、支援 Schema 遷移、整合加密存儲。
框架依賴自製 LogManager,直接註釋相關行即可編譯。源碼約 380 行,無第三方依賴,適合直接複製進專案。
- 上一篇文章: J2ME 遊戲高分存檔:RMS 記錄管理系統完整操作指南
- 下一篇文章: J2ME 遊戲 API 地圖構建利器:TiledLayer 完整解析與貼磚實戰
Copyright ©
程式碼之家 All Rights Reserved