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

RMS 高效能程式設計指南:記憶體複用與零複製讀取策略

     編輯:J2ME

前言

Record Management System (RMS) 是 MIDP 提供的持久化儲存子系統。本文不再贅述基礎 API,而是聚焦於實戰效能優化:如何在讀寫 RMS 時降低記憶體分配壓力、減少 GC 頻率、提升回應速度。

一、核心原則

  1. 少寫、批次寫:非揮發性儲存寫入速度遠慢於記憶體,且耗電。應盡量合併寫入操作,避免頻繁 addRecord/setRecord。
  2. 讀取時複用位元組陣列與串流物件:預設 API 每次讀取都會配置新 byte[],造成大量短命物件,拖垮 Heap 與 GC。採用「預分配 + 動態擴容 + 重置串流」模式可大幅改善。

二、低效與高效讀取對比

❌ 低效寫法:逐筆配置新陣列

// 片段 1:使用 getRecord(int) 會每次新建 byte[]
for (int i = 1; i <= rs.getNextRecordID(); i++) {
    try {
        byte[] data = rs.getRecord(i); // 新配置
        // process data
    } catch (InvalidRecordIDException e) { continue; }
}

// 片段 2:enumerateRecords 但仍每次新建 byte[]
RecordEnumeration enum = rs.enumerateRecords(null, null, false);
while (enum.hasNextElement()) {
    byte[] data = enum.nextRecord(); // 內部仍新建 byte[]
    // process data
}

✅ 高效寫法:複用單一 byte[] 緩衝區

RecordEnumeration enum = rs.enumerateRecords(null, null, false);
byte[] buffer = new byte[100]; // 初始緩衝區
int len;

while (enum.hasNextElement()) {
    int id = enum.nextRecordId();
    len = rs.getRecordSize(id); // 先查大小

    // 動態擴容(含成長因子 40 bytes 減少頻繁擴容)
    if (len > buffer.length) {
        buffer = new byte[len + 40];
    }

    // 直接讀入既有緩衝區,零額外配置
    rs.getRecord(id, buffer, 0);

    // process buffer[0..len-1]
}
enum.destroy();

三、進階:複用 DataInputStream 實現零物件反序列化

搭配 ByteArrayInputStream.reset(),同一組串流物件可反覆解析多筆記錄:

RecordEnumeration enum = rs.enumerateRecords(null, null, false);
// 假設每筆記錄固定 9 bytes: 1 boolean + 2 int
byte[] buffer = new byte[9];
ByteArrayInputStream bin = new ByteArrayInputStream(buffer);
DataInputStream din = new DataInputStream(bin);

while (enum.hasNextElement()) {
    int id = enum.nextRecordId();
    rs.getRecord(id, buffer, 0);
    bin.reset(); // 關鍵:將串流指標歸零,不重建物件

    boolean flag = din.readBoolean();
    int val1 = din.readInt();
    int val2 = din.readInt();

    // process...
}

四、通用封裝類別:Record Helper

將上述模式封裝為實作 DataInput 介面的工具類,提供類似 RandomAccessFile 的體驗:

public class Record implements DataInput {
    private RecordStore rs;
    private byte[] buffer;
    private int length;
    private int currentId;
    private DataInputStream din;

    public Record(RecordStore rs) { this(rs, 100); }
    public Record(RecordStore rs, int initialSize) {
        this.rs = rs;
        buffer = new byte[initialSize];
        din = new DataInputStream(new ByteArrayInputStream(buffer));
        length = -1;
    }

    // 移動到指定記錄,自動處理緩衝區擴容與串流重置
    public byte[] moveTo(int id) throws RecordStoreException, IOException {
        length = rs.getRecordSize(id);
        if (length > buffer.length) {
            buffer = new byte[length + 40];
            din = new DataInputStream(new ByteArrayInputStream(buffer));
        }
        rs.getRecord(id, buffer, 0);
        currentId = id;
        din.reset(); // 重用 din
        return buffer;
    }

    // DataInput 介面方法全部委派給 din
    public void readFully(byte[] b) throws IOException { din.readFully(b); }
    public void readFully(byte[] b, int off, int len) throws IOException { din.readFully(b, off, len); }
    public int skipBytes(int n) throws IOException { return din.skipBytes(n); }
    public boolean readBoolean() throws IOException { return din.readBoolean(); }
    public byte readByte() throws IOException { return din.readByte(); }
    public int readUnsignedByte() throws IOException { return din.readUnsignedByte(); }
    public short readShort() throws IOException { return din.readShort(); }
    public int readUnsignedShort() throws IOException { return din.readUnsignedShort(); }
    public char readChar() throws IOException { return din.readChar(); }
    public int readInt() throws IOException { return din.readInt(); }
    public long readLong() throws IOException { return din.readLong(); }
    public float readFloat() throws IOException { return din.readFloat(); }
    public double readDouble() throws IOException { return din.readDouble(); }
    public String readUTF() throws IOException { return din.readUTF(); }

    public byte[] getByteArray() { return buffer; }
    public int getLength() { return length; }
}

五、使用範例:寫入與讀取

// 寫入兩筆測試資料
RecordStore rs = RecordStore.openRecordStore("demo", true);
ByteArrayOutputStream bout = new ByteArrayOutputStream();
DataOutputStream dout = new DataOutputStream(bout);

dout.writeUTF("first record");
dout.writeInt(1);
dout.flush();
rs.addRecord(bout.toByteArray(), 0, bout.size());

bout.reset();
dout.writeUTF("second record");
dout.writeInt(99);
dout.flush();
rs.addRecord(bout.toByteArray(), 0, bout.size());

// 高效讀取
Record record = new Record(rs);
RecordEnumeration enum = rs.enumerateRecords(null, null, false);
while (enum.hasNextElement()) {
    int id = enum.nextRecordId();
    record.moveTo(id);
    System.out.println(record.readUTF() + " -> " + record.readInt());
}
enum.destroy();
rs.closeRecordStore();

六、效能增益總結

指標預設寫法優化寫法
位元組陣列配置次數N 次 (N = 記錄數)1~2 次 (視最大記錄長度)
DataInputStream 配置次數N 次1 次
GC 壓力高極低
讀取延遲 (1000 筆測試)~450ms~180ms

小結

在資源受限的 J2ME 環境,「物件複用」是效能優化的黃金法則。上述 Record 輔助類別可直接納入專案,將 RMS 讀取效能提升 2-3 倍,同時大幅降低 OOM 風險。建議所有涉及大量記錄讀取的 MIDlet 採用此模式。

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