详解RMS

作者:潇潇  出处:www.j2mefans.com

一、关于记录存储的名字带来的问题

我们知道记录存储就像一个数据
库一样,而且每一个记录存储中的表都是以一个独一无二的名字,作为标识的。其大小不能超过32个Unicode 字元,且大小写有差別。因此同一个jar
包之中的MIDlet都可以共享共享这些记录存储。而不同的jar包之间的MIDlet不可以共享。

这样当我们要打开一个记录存储时较安全的做法是先检查记录存储名称的大小。
public RecordStore openRSAnyway(String rsname)
{
    RecordStore rs = null ;
    //名稱大於32 個字元就不接受
    if(rsname.length() > 32)
        return null ;
    try
    {
        rs = RecordStore.openRecordStore(rsname,true) ;
        return rs ;
    }catch(Exception e)
    {
    return null ;
    }
}
对于之打开已经存在的记录存储的时候,我们可以使用下面的代码:
public RecordStore openRSExisted(String rsname)
{
    RecordStore rs = null ;
    //名稱大於32 個字元就不接受
    if(rsname.length() > 32)
        return null ;
    try
    {
        rs = RecordStore.openRecordStore(rsname,false) ;
        return rs ;
    }catch(Exception e)
    {
        return null ;
    }
}
相应的当要删除一个记录存储时我们也可以这样来写:
public boolean deleteRS(String rsname)
{
    if(rsname.length() > 32)
        return false ;
    try
    {
        RecordStore.deleteRecordStore(rsname) ;
    }catch(Exception e)
    {
        return false ;
    }
    return true ;
}

二、关于不同类型纪录的存储和读取

因为记录存储仅提供写入byte输组,因此当我们写入一般非byte类型的数据时就比较麻烦,我必须写一些方法来做额外的处理:

例如:把整数化成byte数组存入记录存储
public int writeInt2RS(RecordStore rs,int data)
{
    byte []tmp = new byte[4] ;
    tmp[0] = (byte)(0xff&( data >> 24)) ;
    tmp[1] = (byte)(0xff&( data >> 16)) ;
    tmp[2] = (byte)(0xff&( data >> 8)) ;
    tmp[3] = (byte)(0xff&( data >> 0)) ;
    try
    {
        return rs.addRecord(tmp,0,tmp.length) ;
    }catch(Exception e)
    {
    }
    return -1 ;
}
相反将byte数组取出之后转化为整数的方法:
public int readInt4RS(RecordStore rs,int recordid)
{
    byte []tmp = new byte[4] ;
    try
    {
        tmp = rs.getRecord(recordid) ;
    }catch(Exception e)
    {
    }
    int result = tmp[0] ;
    result = (result << 8) + tmp[1] ;
    result = (result << 8) + tmp[2] ;
    result = (result << 8) + tmp[3] ;
    return result ;
}

但是这里有一个问题,因为计算机在内部存储数据的方式的问题,当我们输入负数和字节所能存储的无符号数的最大值时,如255。上面的代码就会出错。

所以我们可以使用java给我们提供的现成的类来实现:
    public int writeInt2RS(RecordStore rs,int data)
    {
          byte[] recordData = Integer.toString(data).getBytes();

          try {
              return rs.addRecord(recordData, 0, recordData.length);
          }
          catch (Exception e) {
            System.err.println("Failed writing hi scores!");
          }
            return -1;
    }

    public int readInt4RS(RecordStore rs,int recordid)
    {
        int len=10;//rs.getRecordSize(recordid);
        byte [] recordData = new byte[len];

        try
        {
            recordData= rs.getRecord(recordid);
        }catch(Exception e)
        {
        }
        int result = (Integer.parseInt(new String(recordData)));
        return result;
    }

由于java中的字符型为16bits,所以上面的程序必须经过修改才能处理文字的存储。
    public int writeChar2RS(RecordStore rs,char data)
    {
        byte []tmp = new byte[2] ;
        tmp[0] = (byte)(0xff&( data >> 8)) ;
        tmp[1] = (byte)(0xff&( data >> 0)) ;
        try
        {
            return rs.addRecord(tmp,0,tmp.length) ;
        }catch(Exception e){
        }
        return -1 ;
    }

    public char readChar4RS(RecordStore rs,int recordid)
    {
        byte []tmp = new byte[2] ;
        try
        {
            tmp = rs.getRecord(recordid) ;
        }catch(Exception e)
        {
        }
        char result = (char)tmp[0] ;
        result = (char)((result << 8) + (char)tmp[1]) ;
        return result ;
    }

三、更多方法
另外,RMS还为我们提供了更多的方法:
getLastModified() ——取得上次修改时间。
getName() —— 取得记录存储的名称。
getNextRecordID() —— 下一条记录的ID。
getNumRecords()—— 取得记录存储之中的纪录的条数。
getSize() —— 取得目前记录存储所占得空间。
getSizeAvailable() —— 看看目前还剩下多少空间可以写入数据。
getVersion() —— 取得记录存储的版本号(每次修改记录存储的资料,版本号就会更新)。

参考:
王森《Java 手機程式設計入門》

由上面的例子,我们开到了为了读取数据,我们要分配一个足够大的数组,如下面的代码所示:
    public int readInt4RS(RecordStore rs,int recordid)
    {
        int len=10;//rs.getRecordSize(recordid);
        byte [] recordData = new byte[len];

        try
        {
            recordData= rs.getRecord(recordid);
        }catch(Exception e)
        {
        }
        int result = (Integer.parseInt(new String(recordData)));
        return result;
    }
根本不能按需分配适当的空间(最起码我没有解决)。

这样我们不管纪录的数据大小都这样分配空间的话,势必对空间造成了浪费。有没有一个更好的方法呢?

java很体贴给我们提供了很多的工具。比如:ByteArrayOutputStream、ByteArrayInputStream、
DataOutputStream、DataInputStream这四个类。
我这里使用王森在《Java 手機程式設計入門》书中提供的一个例子吧。
假设我们要设计一个通讯路程序,里面需要一个资料结构叫做FriendData,其结构如下:
class FriendData
{
    String name ;
    String tel ;
    boolean sex ;
    int age ;
}
在这个结构中我们要存储3种不同类型的记录,并且4个段的长度都不相同。为了要将此类型内的数据转换成byte数组或是将byte数组转换为相应的资料类型。我们可以使用上面的4个类来写decode()函数和encode 函数,
其內容如下:

class FriendData
{
    String name ;
    String tel ;
    boolean sex ;
    int age ;

    public FriendData()
    {
        name = "NO NAME" ;
        name = "NO TEL" ;
        sex = false ;
        age = 0 ;
    }

    public byte[] encode()
    {
        byte[] result = null ;
        try
        {
            ByteArrayOutputStream bos =new ByteArrayOutputStream() ;
            DataOutputStream dos =new DataOutputStream (bos) ;
            dos.writeUTF(name) ;
            dos.writeUTF(tel) ;
            dos.writeBoolean(sex) ;
            dos.writeInt(age) ;
            result = bos.toByteArray() ;
            dos.close() ;
            bos.close() ;
        }catch(Exception e)
        {
        }
        return result ;
    }

    public void decode(byte[] data)
    {
        try
        {
            ByteArrayInputStream bis =new ByteArrayInputStream(data) ;
            DataInputStream dis =new DataInputStream (bis) ;
            name = dis.readUTF() ;
            tel = dis.readUTF() ;
            sex = dis.readBoolean() ;
            age = dis.readInt() ;
            dis.close() ;
            bis.close() ;
        }catch(Exception e)
        {
        }
    }
}

有了这样一个类我们可以使用下面的方法使用该类:

import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import java.io.*;
import javax.microedition.rms.* ;
public class DataRecordStoreTest extends MIDlet implements ItemStateListener
{
    private Display display;

    public DataRecordStoreTest()
    {
        display = Display.getDisplay(this);
    }

    public void startApp()
    {
        String dbname = "testdb" ;
        Form f = new Form("RS Test") ;
        RecordStore rs = openRSAnyway(dbname) ;
        if( rs == null )
        {
//            開啟失敗停止MIDlet
            f.append("Table open fail") ;
        }else
        {
            try
            {
                //构造类
                FriendData fd = new FriendData() ;
                fd.name = "尾小保" ;
                fd.tel = "0805449" ;
                fd.sex = false ;
                fd.age = 17 ;
//                得到数据
                byte tmp[] = fd.encode() ;
//                存储数据
                int id = rs.addRecord(tmp,0,tmp.length) ;
//                构造一个类,用来现实数据
                FriendData fd2 = new FriendData() ;
//                分离数据
                fd2.decode(rs.getRecord(id)) ;
//                显示
                f.append("姓名 :"+ fd2.name) ;
                f.append("\n 電話 :"+ fd2.tel) ;
                if(fd2.sex)
                {
                    f.append("\n 性別 : 男") ;
                }else
                {
                    f.append("\n 性別 : 女") ;
                }
                f.append("\n 年齡 :"+ fd2.age) ;
                rs.closeRecordStore() ;
                deleteRS(dbname) ;
            }
            catch(Exception e)
            {
            }
        }
        display.setCurrent(f) ;
    }
    public void pauseApp()
    {
    }
    public void destroyApp(boolean unconditional)
    {
    }
    public void itemStateChanged(Item item)
    {
    }
    public RecordStore openRSAnyway(String rsname)
    {
        RecordStore rs = null ;
//        名稱大於32 個字元就不接受
        if(rsname.length() > 32)
            return null ;
        try
        {
            rs = RecordStore.openRecordStore(rsname,true) ;
            return rs ;
        }catch(Exception e)
        {
            return null ;
        }
    }
    public boolean deleteRS(String rsname)
    {
        if(rsname.length() > 32)
            return false ;
        try
        {
            RecordStore.deleteRecordStore(rsname) ;
        }catch(Exception e)
        {
            return false ;
        }
        return true ;
    }
}

监听记录仓库的变化

记录管理系统提供了一個监听机制,让我們可以随时了解记录管理系统中的资料情況,比如说是否有一条新的记录加入记录仓库、纪录是否被刪除、或者是否有记录被修改等等。
这套监视系统是使用RecordListener接口来完成的。
RecordListener 支持multicast,也就是同时可以有許多人注册监视记录存储的状况。
当有记录被添加入记录仓库时就会触发recordAdded方法。

public void recordAdded(RecordStore rs,int recordid)
{
    try
    {
        byte []tmp = rs.getRecord(recordid) ;
        System.out.println("Record " + recordid + "is added.") ;
        System.out.println("Content=" + tmp[0] + tmp[1]) ;
    }catch(Exception e)
    {
        System.out.println(e.getMessage()) ;
    }
}
纪录被刪除时会触发recordDeleted方法:
public void recordDeleted(RecordStore rs,int recordid)
{
    try
    {
//        請勿在此使用byte []tmp = rs.getRecord(recordid) ;
//        因為此時該筆記錄已被刪除
        System.out.println("Record " + recordid + "isdeleted.") ;
    }catch(Exception e)
    {
        System.out.println(e.getMessage()) ;
    }
}

有记录被修改是触发recordChanged方法。

public void recordChanged(RecordStore rs,int recordid)
{
    try
    {
        byte []tmp = rs.getRecord(recordid) ;
        System.out.println("Record " + recordid + "ischanged.") ;
        System.out.println("Content=" + tmp[0] + tmp[1]) ;
        `}catch(Exception e)
        {
            System.out.println(e.getMessage()) ;
        }
}
三个方法都以记录存储和记录ID作为参数。
使用範例如下:
RLTest.java
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import java.io.*;
import javax.microedition.rms.* ;
public class RLTest extends MIDlet implements RecordListener
{
    private Display display;
    public RLTest()
    {
        display = Display.getDisplay(this);
    }
    public void startApp()
    {
        String dbname = "testdb" ;
        Form f = new Form("RS Test") ;
        RecordStore rs = openRSAnyway(dbname) ;
        rs.addRecordListener(this) ;
        if( rs == null )
        {
//            開啟失敗停止MIDlet
            f.append("Table open fail") ;
        }else
        {
            try
            {
                byte []data = new byte[2] ;
                data[0] = 15 ;
                data[1] = 16 ;
                int id = rs.addRecord(data,0,data.length) ;
                data[0] = 25 ;
                data[1] = 26 ;
                rs.setRecord(id,data,0,data.length) ;
                rs.deleteRecord(id) ;
                rs.addRecord(data,0,data.length) ;
                rs.closeRecordStore() ;
                deleteRS(dbname) ;
            }catch(Exception e)
            {
            }
        }
        display.setCurrent(f) ;
    }
    public void pauseApp()
    {
    }
    public void destroyApp(boolean unconditional)
    {
    }
    public void recordAdded(RecordStore rs,int recordid)
    {
        try
        {
            byte []tmp = rs.getRecord(recordid) ;
            System.out.println("Record " + recordid + "is added.") ;
            System.out.println("Content=" + tmp[0] + tmp[1]) ;
        }catch(Exception e)
        {
            System.out.println(e.getMessage()) ;
        }
    }
    public void recordChanged(RecordStore rs,int recordid)
    {
        try
        {
            byte []tmp = rs.getRecord(recordid) ;
            System.out.println("Record " + recordid + "ischanged.") ;
            System.out.println("Content=" + tmp[0] + tmp[1]) ;
            `}catch(Exception e)
            {
                System.out.println(e.getMessage()) ;
            }
    }
    public void recordDeleted(RecordStore rs,int recordid)
    {
        try
        {
//            請勿在此使用byte []tmp = rs.getRecord(recordid) ;
//            因為此時該筆記錄已被刪除
            System.out.println("Record " + recordid + "isdeleted.") ;
        }catch(Exception e)
        {
            System.out.println(e.getMessage()) ;
        }
    }
    public RecordStore openRSAnyway(String rsname)
    {
        RecordStore rs = null ;
//        名稱大於32 個字元就不接受
        if(rsname.length() > 32)
            return null ;
        try
        {
            rs = RecordStore.openRecordStore(rsname,true) ;
            return rs ;
        }catch(Exception e)
        {
            return null ;
        }
    }
    public boolean deleteRS(String rsname)
    {
        if(rsname.length() > 32)
            return false ;
        try
        {
            RecordStore.deleteRecordStore(rsname) ;
        }catch(Exception e)
        {
            return false ;
        }
        return true ;
    }
}

需要说明的是这些回调函数都是在记录存储完成动作之后才被调用,而非之前,因此在示例之中唯一需要留意的地方只有在recordDeleted()函数。
我們recordAdded()或recordChanged()里面仍然可以利用getRecord()取出该记录,但是无法在
recordDeleted()函式之中这样做。因为我們的函数是在纪录被刪除之后才被调用的,因此該记录早已不复存在了,所以请勿在
recordDeleted()函数里头使用getRecord()取得刚被刪除的资料。

很多时候,我们要遍历纪录仓库来逐一察看纪录。这就需要我们要遍历纪录仓库。
到目前为止,我们可以看到不管我们对纪录做任何变化,都需要纪录id,所以和直接的想法就是使用FOR循环来遍历整个记录仓库。
for(int i = 1 ; i < rs.getNextRecordID() ; i++)
{
byte []data = rs.getRecord(i) ;
… …
}
但是,这正方法确有一个严重的缺点:就是在遍历数据仓库的过程中,所使用的纪录id必须存在,可以随能保证我们添加纪录之后不回去删除呢?这是我们取得的byte数组将是null.所以上述方法是不安全的。

纪录管理系统为我们提供了一种安全的方法——RecordEnumeration类。

RecordEnumeration就像是一个指向记录仓库得指示器。我们們可以使用如下图示来说明这一抽象概念:
 
也就是说,一旦我们取得RecordEnumeration,就可以在记录仓库中来回移动指针,取得存储在其中的资料。
理解了前面的内容,我们还是看一看实际上RecordEnumeration 的设计概念吧。如下图所示:
 
由上图我们可以看出,RecordEnumeration 并非指向记录本身,而是指向记录与记录之间。

我们建立了RecordEnumeration 之后,首先他会指向Before First 和第一条记录之间,然后我们可以利用
hasNextElement()察看之后是否还有记录,当RecordEnumeration 移动到After Last 与最后一条记录中间时,
 hasNextElement() 就传回false 。
hasPreviousElement()方法的用法和hasNextElement()相反,如果需要从最后一条记录往前移动,那么hasPreviousElement()会在指针移动到Before First 和第一条记录之间时传回false。


要注意的是:调用hasNextElement()、hasPreviousElement()、nextRecordID()、
previousRecordID()方法并不会改变RecordEnumeration 的指针位置,这些方法只是取得相关记录而已。真正能够改变
RecordEnumeration 位置的方法是nextRecord()和previousRecord()。

其中:
nextRecord() 會傳回下一条记录的byte数组,并将RecordEnumeration 下移一个记录与记录之间的空格;相反的,
previousRecord() 回传回上一条记录的byte数组,并将RecordEnumeration 上移一个记录与纪录之间的空格。

还有一个方法也是很重要的reset()。任何时候,我们都可以调用reset()方法将RecordEnumeration指针重置到Before First与第一条记录之间。

也可以使用numRecords()方法取得记录仓库中的记录数目。

最后当使用完之后,建议大家使用destory()方法来释放RecordEnumeration类对象和遍历纪录仓库所使用的系统资源。
如果常常对记录仓库增加、修改、刪除纪录,那么最好在遍历纪录仓库前使用rebuild()方法重建RecordEnumeration。
RecordEnumeration 的用法如下:

public void travelRS(RecordStore rs)
{
    try
    {
        RecordEnumeration re =
            rs.enumerateRecords(null,null,false) ;
        System.out.println("There are " +
                re.numRecords() + " in RecordStore") ;
        for(int i = 0 ; i < re.numRecords() ; i++)
        {
            int id = re.nextRecordId() ;
            byte tmp[] = rs.getRecord(id) ;
            System.out.println(tmp[0] + " " + tmp[1]) ;
        }
    }
    catch(Exception e)
    {
    }
}
或者
public void travelRS(RecordStore rs)
{
    try
    {
        RecordEnumeration re =
            rs.enumerateRecords(null,null,false) ;
        System.out.println("There are " + re.numRecords() +
        " in RecordStore") ;
        while(re.hasNextElement())
        {
            byte tmp[] = re.nextRecord() ;
            System.out.println(tmp[0] + " " + tmp[1]) ;
        }
    }
    catch(Exception e)
    {
    }
}
完整的使用示例如下:

import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import java.io.*;
import javax.microedition.rms.* ;
public class RETest extends MIDlet
{
    private Display display;
    public RETest()
    {
        display = Display.getDisplay(this);
    }
    public void startApp()
    {
        String dbname = "testdb" ;
        Form f = new Form("RE Test") ;
        RecordStore rs = openRSAnyway(dbname) ;
        if( rs == null )
        {
//            開啟失敗停止MIDlet
            f.append("Table open fail") ;
        }else
        {
            try
            {
                byte []data = new byte[2] ;
                data[0] = 15 ;
                data[1] = 16 ;
                rs.addRecord(data,0,data.length) ;
                data[0] = 25 ;
                data[1] = 26 ;
                rs.addRecord(data,0,data.length) ;
                data[0] = 35 ;
                data[1] = 36 ;
                rs.addRecord(data,0,data.length) ;
                travelRS(rs) ;
                rs.closeRecordStore() ;
                deleteRS(dbname) ;
            }catch(Exception e)
            {
            }
        }
        display.setCurrent(f) ;
    }
    public void pauseApp()
    {
    }
    public void destroyApp(boolean unconditional)
    {
    }
    public void travelRS(RecordStore rs)
    {
        try
        {
            RecordEnumeration re =
                rs.enumerateRecords(null,null,false) ;
            System.out.println("There are " + re.numRecords() +
            " in RecordStore") ;
            while(re.hasNextElement())
            {
                byte tmp[] = re.nextRecord() ;
                System.out.println(tmp[0] + " " + tmp[1]) ;
            }
        }
        catch(Exception e)
        {
        }
    }
    public RecordStore openRSAnyway(String rsname)
    {
        RecordStore rs = null ;
//        名稱大於32 個字元就不接受
        if(rsname.length() > 32)
            return null ;
        try
        {
            rs = RecordStore.openRecordStore(rsname,true) ;
            return rs ;
        }catch(Exception e)
        {
            return null ;
        }
    }
    public boolean deleteRS(String rsname)
    {
        if(rsname.length() > 32)
            return false ;
        try
        {
            RecordStore.deleteRecordStore(rsname) ;
        }catch(Exception e)
        {
            return false ;
        }
        return true ;
    }
}