Record Management System是J2ME的一个重要的子系统,目的是实现应用程序本地数据的持久性存储。目前支持文件系统的移动信息设备还有限,因此Record Management System是J2ME开发人员实现本地数据存储的首选途径。本文的目的就是全面的介绍Record Management System的知识。
顾名思义Record Management System是管理数据的系统,Record是系统中最重要的实体。在移动设备存储空间存储的并不是字段,而是字节数组。Mobile Infomation Device Profile(MIDP)规范中并没有规定什么样的数据才能存储为记录,事实上记录是任何可以被字节数组表示的数据,例如图片、文本等。Record Management System的职责是存储和唯一标识记录,而表示数据的任务是由应用程序来完成的,因此J2ME的开发人员往往要花费更多的精力来处理存储空间中的数据。这样做的目的是简化MIDP的实现,使得J2ME的子系统尽量的小巧、灵活。毕竟移动信息设备的存储空间和处理器的能力都有限。
Record Store是一系列记录的有序集合,记录是不能单独存在的,必须属于Record Store。Record Store保证记录的读写操作都是原子的,数据不会被破坏。在API中Record Store是由javax.microedition.rms.RecordStore实现的,关于RecordStore的具体操作在接下来的文章中会有详细的介绍。
MIDP规范中说明移动信息设备要提供至少8K的非易失性存储空间给应用程序来实现数据的持久性存储。但是不同的设备提供的空间并不相同。如果MIDlet suite使用了Record Management System,那么它必须在MANIFEST文件和JAD文件中通过设置MIDlet-Data-Size来说明它所需要的最小的数据存储空间,单位是字节,例如MIDlet-Data-Size:8192。如果你的值超过了移动设备规定的最大值那么你的应用程序将不能正确安装。这个值并不是移动设备真正提供给应用程序的最大Record Management System的存储空间,往往要大一些,因此开发人员应该避免把应用程序需要的最小存储空间设置的过大,必要的时候应该参考相关设备的说明手册。在非易失性存储空间内读写数据往往速度会比较慢,因此针对频繁访问的数据最好提供缓存的机制来提供性能。Record Management System的读写操作是线程安全的,但是由于Record Store是被整个MIDlet suite共享的,所以如果不同MIDlet上运行的线程操作Record Store的时候,我们应该进行必要的线程同步,避免数据被破坏。
MIDP1.0和MIDP2.0中关于Record Management System的实现有些不同,在同一个MIDlet suite里面的MIDlets可以相互访问彼此的Record Store。但是在MIDP1.0的实现中,并没有提供在不同MIDlet suite之间共享Record Store的机制。在MIDP2.0中提供的了新的API来解决不同MIDlet suite之间共享Record Store的问题,在创建Record Store的时候通过授权模式和读写控制参数来进行共享机制的管理,我将在下篇文章中进行详细的介绍。
加强对Record Management System的理解的最好的办法就是进行实际的开发,在进行开发中我发现并不是所有移动设备的MIDP实现都准确无误。当我用getSizeAvaliable()方法查询Nokia6108的可用Record Store空间的时候得到的数值是超过1M字节,但是当我写入40K的数据的时候就出现了RecordStoreFullException异常,因此我编写了一个自动测试手机Record Store最大存储空间的软件。原理是每隔一定时间例如100-500毫秒向Record Store内写入1K字节的数据,当抛出存储空间已满的异常的时候就可以得到最大值了,精确单位为K字节。下面是程序的源代码和JAD文件的内容,开发平台为Eclipse3.0RC2+EclipseME0.4.1+Wtk2.1+J2SDK1.4.2._03,在真机Nokia 6108上测试通过并显示最大值为31K。(请不要在模拟器上进行测试,那样结果没有意义)
总结:本文只是带领读者对Record Management System进行了大概的了解,虽然在文章最后提供了一个应用程序。但是并没有深入分析如何使用Record Management System。在接下来的文章中我们会深入分析javax.microedition.rms包中的类,重点是如何使用RecordStore类。
import javax.microedition.lcdui.Alert;
import javax.microedition.lcdui.AlertType;
import javax.microedition.lcdui.Display;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;
import javax.microedition.rms.RecordStoreException;
public class RMSAnalyzer extends MIDlet
{
private Display display;
private CounterCanvas counterCanvas;
private Alert alert;
protected void startApp() throws MIDletStateChangeException
{
display = Display.getDisplay(RMSAnalyzer.this);
alert = new Alert("错误提示");
try
{
String interval = this.getAppProperty("INTER");
int t = Integer.parseInt(interval);
counterCanvas = new CounterCanvas(t, 1, this);
}
catch (RecordStoreException e)
{
this.showAlertError(e.getMessage());
}
display.setCurrent(counterCanvas);
}
public Display getDisplay()
{
return display;
}
protected void pauseApp()
{
}
protected void destroyApp(boolean arg0) throws MIDletStateChangeException
{
}
public void showAlertError(String message)
{
alert.setString(message);
alert.setType(AlertType.ERROR);
alert.setTimeout(3000);
display.setCurrent(alert);
}
}
import java.util.Timer;
import java.util.TimerTask;
import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Graphics;
import javax.microedition.midlet.MIDletStateChangeException;
import javax.microedition.rms.*;
public class CounterCanvas extends Canvas implements CommandListener
{
private RMSModel model;
private RMSAnalyzer RMSanalyzer;
private int interTime;
private int counter;
private boolean go = true;
public static Command backCommand = new Command("退出", Command.EXIT, 3);
public static final int INC = 1;
public final Timer timer = new Timer();
public CounterCanvas(int interTime, int base, RMSAnalyzer rmsa)
throws RecordStoreException
{
this.interTime = interTime;
this.counter = base;
this.RMSanalyzer = rmsa;
model = new RMSModel(base, RMSanalyzer);
this.addCommand(backCommand);
this.setCommandListener(this);
TimerTask timerTask = new TimerTask()
{
public void run()
{
try
{
model.writeRecord(INC);
counter++;
} catch (RecordStoreFullException e)
{
go = false;
model.deleteRMS();
timer.cancel();
} catch (RecordStoreException e)
{
model.deleteRMS();
RMSanalyzer.showAlertError(e.getMessage());
timer.cancel();
}
repaint();
}
};
timer.schedule(timerTask, 1000, interTime);
}
public void setCounter(int counter)
{
this.counter = counter;
}
public void setInterTime(int interTime)
{
this.interTime = interTime;
}
protected void paint(Graphics arg0)
{
int SCREEN_WIDTH = this.getWidth();
int SCREEN_HEIGHT = this.getHeight();
arg0.drawRect(SCREEN_WIDTH / 10, SCREEN_HEIGHT / 2,
SCREEN_WIDTH * 4 / 5, 10);
if(RMSanalyzer.getDisplay().isColor())
{
arg0.setColor(128, 128, 255);
}
arg0.fillRect(SCREEN_WIDTH / 10, SCREEN_HEIGHT / 2, counter, 10);
if (!go)
arg0.drawString("最大值:" + counter+"K字节", 0, 0, Graphics.TOP
| Graphics.LEFT);
}
public void commandAction(Command arg0, Displayable arg1)
{
if (arg0 == backCommand)
{
try
{
model.deleteRMS();
RMSanalyzer.destroyApp(false);
RMSanalyzer.notifyDestroyed();
} catch (MIDletStateChangeException e)
{
}
}
}
}
import javax.microedition.rms.*;
public class RMSModel
{
public static final int K = 1024;
private RecordStore rs;
private int baseCount;
private RMSAnalyzer RMSanalyzer;
public static final String name = "test";
public RMSModel(int baseCount, RMSAnalyzer rmsa)
throws RecordStoreException
{
this.baseCount = baseCount;
this.RMSanalyzer = rmsa;
if (rs == null)
{
rs = RecordStore.openRecordStore(name, true);
writeRecord(baseCount);
}
}
public void writeRecord(int count) throws RecordStoreException
{
byte[] data = new byte[count * K];
for (int i = 0; i < count; i++)
{
data[i] = 1;
}
rs.addRecord(data, 0, count * K);
}
public void deleteRMS()
{
try
{
rs.closeRecordStore();
RecordStore.deleteRecordStore(name);
} catch (RecordStoreException e)
{
RMSanalyzer.showAlertError(e.getMessage());
}
}
}
RMSAnalyzer.jad
MIDlet-Jar-Size: 5293
MIDlet-1: RMSAnalyzer,,RMSAnalyzer
MIDlet-Jar-URL: RMSAnalyzer.jar
MicroEdition-Configuration: CLDC-1.0
MIDlet-Version: 1.0.0
MIDlet-Name: RMSAnalyzer
MIDlet-Data-Size: 8192
MIDlet-Vendor: Midlet Suite Vendor
MicroEdition-Profile: MIDP-1.0
INTER: 100
RMS从入门到精通之二
[ 作者:mingjava 转贴自:本站原创 点击数:214 更新时间:2004-8-17 文章录入:admin ]
在系列之一中,我们着重讲述了Record Management System的基础知识。在介绍如何使用Record Management System之前,我想首先介绍一下Java IO以及在J2ME平台实现序列化的内容,无论对Record Management System还是MIDP中的通用联网框架来说,上述内容都是非常重要的。
在CLDC中定义的Java IO是非常短小精悍的,但是也提供了足够的类来完成我们的IO操作。由于和J2SE的实现是通用的,因此你可以使用J2ME和J2SE或者J2EE平台进行通信。比如通过联网和servlet进行通信。在Record Management System中我们主要使用的类是ByteArrayInputStream、ByteArrayOutputStream、DataInputStream和DataOutputStream。前面两个是基于字节的,ByteArrayInputStream的作用是把字节数组转换成流而ByteArrayOutputStream的作用是把内存缓冲区内的数据转换成字节。后面两个类是基于java基本数据类型和String操作的。通常他们把前面两个类作为参数传送给构造器,这样他们就可以对基本数据类型以及String进行读写操作了。值得注意的一点是ByteArrayOutputStream的toByteArray()方法是把内存中的数据进行复制返回,这样的话多浪费了一份内存,为了更有效的使用有限的存储空间你可以扩展ByteArrayOutputSteam类然后提供getByteArray()方法,下面是例子:
public class MyByteArrayOutputStream extends ByteArrayOutputStream
{
public byte[] getByteArray()
{
return buf;
}
}
在J2ME中并没有提供对象序列化的机制,但是我们可以自己实现它。请考虑下面这个类:
public class Bank
{
private String bankName;
private String phone;
private int employeeNum;
public Bank(){}
public Bank(String aBankName,String aPhone,int aEmployeeNum)
{
this.bankName = aBankName;
this.phone = aPhone;
this.employeeNum = aEmployeeNum;
}
public String getBankName()
{
return bankName !=null?bankName:"";
}
public String getPhone()
{
return phone!=null?phone:"";
}
public int getEmployeeNum()
{
return employeeNum;
}
}
我们添加两个方法到这个类来实现对象序列化。如下所示:
public class Bank
{
private String bankName;
private String phone;
private int employeeNum;
public Bank(){}
public Bank(String aBankName,String aPhone,int aEmployeeNum)
{
this.bankName = aBankName;
this.phone = aPhone;
this.employeeNum = aEmployeeNum;
}
public String getBankName()
{
return bankName !=null?bankName:"";
}
public String getPhone()
{
return phone!=null?phone:"";
}
public int getEmployeeNum()
{
return employeeNum;
}
public byte[] serialize() throws IOException
{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
dos.writeUTF(getBankName());
dos.writeUTF(getPhone());
dos.writeInt(getEmployeeNum());
dos.flush();
return bos.toByteArray();
}
public Bank deserialize(byte[] data) throws IOException
{
ByteArrayInputStream bis = new ByteArrayInputStream(data);
DataInputStream dis = new DataInputStream(bis);
Bank myBank = new Bank();
myBank.bankName = dis.readUTF();
myBank.phone = dis.readUTF();
myBank.employeeNum = dis.readInt();
return myBank;
}
}
这样我们就实现了对象的序列化,使用起来也非常简单。序列化和反序列化的操作分别如下面所示:
Bank aBank = .....;
RecordStore rs = .....;
try
{
byte[] data = aBank.serialize();
rs.addRecord(data,0,data.length);
}
catch(IOException e)
{
//do something
}
catch(RecordStoreException e)
{
//do something
}
————————————————————————————
byte[] data = ..........;
Bank aBank = null;
try
{
aBank = Bank.deserialize(data);
}
catch(IOException e)
{
}
值得注意的一点是在Bank类中我们的成员都是基本数据类型以及String类型,并不存在指向其他对象的引用,这是最理想也是最简单的情况,事实上我们在J2ME中设计序列化的类的时候也应该尽量这样做,避免不必要得麻烦。
RMS从入门到精通之三
[ 作者:mingjava 转贴自:本站原创 点击数:417 更新时间:2004-8-17 文章录入:admin ]
前面两篇文章详细的介绍了Record Management System的基本概念以及对象序列化的问题,现在我们主要介绍关于RecordStore类的使用,在SUN的网站提供了一个RMSAnalyzer类,你可以把他用在你的项目中来调试你的程序。
Record Store Discovery
你可以通过调用RecordStore.listRecordStores()来得到MIDlet suites中的Record Store,这个静态方法返回一个String类型的数组,每个代表Record Store的名字,如果没有record Store那么会返回null,方法RMSAnalyzer.annlyzeAll()通过调用listRecordStores()得到Record Store然后通过方法analyze()分析每个Record Store.
public void analyzeAll(){
String[] names = RecordStore.listRecordStores();
for( int i = 0;
names != null && i < names.length;
++i ){
analyze( names[i] );
}
}
注意到列出的数组名字是所属MIDlet suite的Record Store。MIDP中没有提供列举出任何其他MIDlet suites的Record Store的方法,在MIDP 1.0中Record Store在所属MIDlet suites外是不可见的,在MIDP 2.0中,MIDlet suite可以指定一个Record Store作为可共享的,但是其他的suite要知道他的名字才可以访问它。
Opening and closing Record Store
RecordStore.openRecordStore()是用来打开一个Record Store的,它也可以用来创建一个Record Store,这个静态方法返回一个Record Store的对象,下面是RMSAnalyzer.analyze()。
public void analyze( String rsName ){
RecordStore rs = null;
try {
rs = RecordStore.openRecordStore( rsName, false );
analyze( rs ); // call overloaded method
} catch( RecordStoreException e ){
logger.exception( rsName, e );
} finally {
try {
rs.closeRecordStore();
} catch( RecordStoreException e ){
// Ignore this exception
}
}
}
openRecordStore()的第二个参数表示如果record store不存在是不是创建新的,在MIDP2.0中,如果你想打开一个在其他的MIDlet suite里面创建的Record Store的话应该用下面的方法。
...
String name = "mySharedRS";
String vendor = "EricGiguere.com";
String suite = "TestSuite";
RecordStore rs =
RecordStore.openRecordStore( name, vendor, suite );
...
vendor和suite的名字应该和MIDlet suite的manifest和jad的内容一致。
当你完成了对record store的操作以后应该调用RecordStore.closeRecordStore()来关闭它,一个RecordStore的实例在一个MIDlet suite里面是唯一的,如果以同样的名字再次调用openRecordStore()的话会返回同样的实例,这样多个MIDlet在共享一个record store,每个record store会跟踪它被打开的次数,这个record store直到被调用相同次数的closeRecordStore()后才会彻底的关闭,对一个已经关闭的Record Store进行操作会导致抛出
RecordStoreNotOpenException。
Creating Record Store
创建一个私有的record store,把第二个参数设置为true调用openRecordStore(),...
// Create a record store
RecordStore rs = null;
try {
rs = RecordStore.openRecordStore( "myrs", true );
} catch( RecordStoreException e ){
// couldn't open it or create it
}
如果要创建一个可共享的record store,那么使用四个参数变量的openRecordStore()
int authMode = RecordStore.AUTHMODE_ANY;
boolean writable = true;
rs = RecordStore.openRecordStore( "myrs", true,
authMode, writable );
当第二个参数是true并且record store不存在的时候,后面两个参数控制他的授权模式和可写性,授权模式决定是否其他的MIDlet suite具有访问record store的权限,两种可能的模式是RecordStore.AUTHMODE_PRIVATE(只有拥有的SUITE才可以访问)和RecordStore.AUTHMODE_ANY(任何suite都可以访问),可写性控制着是否其他的suite能够修改record store,如果false的话,那么只有所属suite才可以修改,其他的只能读取。注意所属suite可以在任何时候调用RecordStore.setMode()来修改它的授权和读写模式,例如:
rs.setMode( RecordStore.AUTHMODE_ANY, false );事实上最好是创建一个record store,授权模式为RecordStore.AUTHMODE_PRIVATE。
Adding and updating records
记录就是字节数组,你可以通过调用RecordStore.addRecord()来添加一个新的记录到一个打开的Record Store
byte[] data = new byte[]{ 0, 1, 2, 3 };
int recordID;
recordID = rs.addRecord( data, 0, data.length );
如果第一个参数是null的话,那么你就能添加一个空记录。第二个和第三个参数说明了字节数组的起点和从起点开始的总的字节数。如果添加成功会返回新记录的record ID,如果失败会抛出异常,例如RecordStoreFullException。
通过调用RecordStore.setRecord()可以在任何时候更新记录的内容。
int recordID = ...; // some record ID
byte[] data = new byte[]{ 0, 10, 20, 30 };
rs.setRecord( recordID, data, 1, 2 );
// replaces all data in record with 10, 20
你不能大块的添加记录,必须首先把记录转换成数组,然后调用添加的函数,通过调用RecordStore.getNextRecordID()你可以得到下次调用addRecord()将要得到的record ID,这个值比现在使用的任何值都大。
Reading records
想要读取记录,有两种方法。第一是分配合适大小的数组然后把记录的内容复制过去。
int recordID = .... // some record ID
byte[] data = rs.getRecord( recordID );
第二种方法是把数组复制到预先分配的字节数组中,指定复制的起点并返回复制的字节数目。
int recordID = ...; // some record ID
byte[] data = ...; // an array
int offset = ...; // the starting offset
int numCopied = rs.getRecord( recordID, data, offset );
数组的大小必须能足够容纳数据,否则会抛出java.lang.ArrayIndexOutOfBoundsException.使用RecordStore.getRecordSize()来分配数组空间是合适的方法。事实上第一个方法等价与
byte[] data = new byte[rs.getRecordSize(recordID)];
rs.getRecord(recordID,data,0);
第二种方法有利于减小内存的分配,当你要遍历一组记录的时候,你可以结合getNextRecordID()和getRecordSize()来完成运算量很大的搜索。
int nextID = rs.getNextRecordID();
byte[] data = null;
for( int id = 0; id < nextID; ++id ){
try {
int size = rs.getRecordSize( id );
if( data == null || data.length < size ){
data = new byte[ size ];
}
rs.getRecord( id, data, 0 );
processRecord( rs, id, data, size ); // process it
} catch( InvalidRecordIDException e ){
// ignore, move to next record
} catch( RecordStoreException e ){
handleError( rs, id, e ); // call an error routine
}
}
更好的办法是使用RecordStore.enumerateRecords()来遍历记录。
Deleting Records and Record Stores
你可以通过调用RecordStore.deleteRecord()来删除记录
int recordID = ...; // some record ID
rs.deleteRecord( recordID );
一旦记录被删除,任何对记录的操作将会导致抛出InvalidRecordIDException,通过调用RecordStore.deleteRecordStore()来删除Record Store.
try {
RecordStore.deleteRecordStore( "myrs" );
} catch( RecordStoreNotFoundException e ){
// no such record store
} catch( RecordStoreException e ){
// somebody has it open
}
Record Store只能在没有打开的时候被所属的suite的MIDlet删除。
其他操作
getLastModified()返回最后修改record store的时间,格式和System.currentTimeMillis()一样。
getName()得到record store的名字。
getNumRecords()返回record store中记录的数量。
getSize()返回record store的整个大小,包括记录的大小和系统来实现record store的空间。
getSizeAvailable()返回record store中还能用的空间,
getVersion()返回record store的版本数,这个数大于0,每次record store被修改,这个数都会自动增加。
一个MIDlet能够通过注册一个监听器来跟踪record store,通过addRecordListener()和deleteRecordListener()。
The RMSAnalyzer class
最后我们提供一个分析record store的类,你可以这样使用它。
RecordStore rs = ...; // open the record store
RMSAnalyzer analyzer = new RMSAnalyzer();
analyzer.analyze( rs );
通常分析输出到System.out,样式如下所示:
=========================================
Record store: recordstore2
Number of records = 4
Total size = 304
Version = 4
Last modified = 1070745507485
Size available = 975950
Record #1 of length 56 bytes
5f 62 06 75 2e 6b 1c 42 58 3f _b.u.k.BX?
1e 2e 6a 24 74 29 7c 56 30 32 ..j$t)|V02
5f 67 5a 13 47 7a 77 68 7d 49 _gZ.Gzwh}I
50 74 50 20 6b 14 78 60 58 4b PtP k.x`XK
1a 61 67 20 53 65 0a 2f 23 2b .ag Se./#+
16 42 10 4e 37 6f .B.N7o
Record #2 of length 35 bytes
22 4b 19 22 15 7d 74 1f 65 26 "K.".}t.e&
4e 1e 50 62 50 6e 4f 47 6a 26 N.PbPnOGj&
31 11 74 36 7a 0a 33 51 61 0e 1.t6z.3Qa.
04 75 6a 2a 2a .uj**
Record #3 of length 5 bytes
47 04 43 22 1f G.C".
Record #4 of length 57 bytes
6b 6f 42 1d 5b 65 2f 72 0f 7a koB.[e/r.z
2a 6e 07 57 51 71 5f 68 4c 5c *n.WQq_hL\
1a 2a 44 7b 02 7d 19 73 4f 0b .*D{.}.sO.
75 03 34 58 17 19 5e 6a 5e 80 u.4X..^j^?
2a 39 28 5c 4a 4e 21 57 4d 75 *9(\JN!WMu
80 68 06 26 3b 77 33 ?h.&;w3
Actual size of records = 153
-----------------------------------------
这种样式方便在wtk中显示,在实际的设备中进行测试的时候,你可能希望把分析输出到串口或者通过网络发到servlet,你可以通过定义自己的类实现实现Logger接口,然后把这个类作为RMSAnalyzer构造器的参数。下面是源代码。
package com.ericgiguere;
import java.io.*;
import javax.microedition.rms.*;
// Analyzes the contents of a record store.
// By default prints the analysis to System.out,
// but you can change this by implementing your
// own Logger.
public class RMSAnalyzer {
// The logging interface.
public interface Logger {
void logEnd( RecordStore rs );
void logException( String name, Throwable e );
void logException( RecordStore rs, Throwable e );
void logRecord( RecordStore rs, int id,
byte[] data, int size );
void logStart( RecordStore rs );
}
private Logger logger;
// Constructs an analyzer that logs to System.out.
public RMSAnalyzer(){
this( null );
}
// Constructs an analyzer that logs to the given logger.
public RMSAnalyzer( Logger logger ){
this.logger = ( logger != null ) ? logger :
new SystemLogger();
}
// Open the record stores owned by this MIDlet suite
// and analyze their contents.
public void analyzeAll(){
String[] names = RecordStore.listRecordStores();
for( int i = 0;
names != null && i < names.length;
++i ){
analyze( names[i] );
}
}
// Open a record store by name and analyze its contents.
public void analyze( String rsName ){
RecordStore rs = null;
try {
rs = RecordStore.openRecordStore( rsName, false );
analyze( rs );
} catch( RecordStoreException e ){
logger.logException( rsName, e );
} finally {
try {
rs.closeRecordStore();
} catch( RecordStoreException e ){
// Ignore this exception
}
}
}
// Analyze the contents of an open record store using
// a simple brute force search through the record store.
public synchronized void analyze( RecordStore rs ){
try {
logger.logStart( rs );
int lastID = rs.getNextRecordID();
int numRecords = rs.getNumRecords();
int count = 0;
byte[] data = null;
for( int id = 0;
id < lastID && count < numRecords;
++id ){
try {
int size = rs.getRecordSize( id );
// Make sure data array is big enough,
// plus add some for growth
if( data == null || data.length < size ){
data = new byte[ size + 20 ];
}
rs.getRecord( id, data, 0 );
logger.logRecord( rs, id, data, size );
++count; // only increase if record exists
}
catch( InvalidRecordIDException e ){
// just ignore and move to the next one
}
catch( RecordStoreException e ){
logger.logException( rs, e );
}
}
} catch( RecordStoreException e ){
logger.logException( rs, e );
} finally {
logger.logEnd( rs );
}
}
// A logger that outputs to a PrintStream.
public static class PrintStreamLogger implements Logger {
public static final int COLS_MIN = 10;
public static final int COLS_DEFAULT = 20;
private int cols;
private int numBytes;
private StringBuffer hBuf;
private StringBuffer cBuf;
private StringBuffer pBuf;
private PrintStream out;
public PrintStreamLogger( PrintStream out ){
this( out, COLS_DEFAULT );
}
public PrintStreamLogger( PrintStream out, int cols ){</P< p>
this.out = out;
this.cols = ( cols > COLS_MIN ? cols : COLS_MIN );
}
private char convertChar( char ch ){
if( ch < 0x20 ) return '.';
return ch;
}
public void logEnd( RecordStore rs ){
out.println( "\nActual size of records = "
+ numBytes );
printChar( '-', cols * 4 + 1 );
hBuf = null;
cBuf = null;
pBuf = null;
}
public void logException( String name, Throwable e ){
out.println( "Exception while analyzing " +
name + ": " + e );
}
public void logException( RecordStore rs, Throwable e ){
String name;
try {
name = rs.getName();
} catch( RecordStoreException rse ){
name = "";
}
logException( name, e );
}
public void logRecord( RecordStore rs, int id,
byte[] data, int len ){
if( len < 0 && data != null ){
len = data.length;
}
hBuf.setLength( 0 );
cBuf.setLength( 0 );
numBytes += len;
out.println( "Record #" + id + " of length "
+ len + " bytes" );
for( int i = 0; i < len; ++i ){
int b = Math.abs( data[i] );
String hStr = Integer.toHexString( b );
if( b < 0x10 ){
hBuf.append( '0');
}
hBuf.append( hStr );
hBuf.append( ' ' );
cBuf.append( convertChar( (char) b ) );
if( cBuf.length() == cols ){
out.println( hBuf + " " + cBuf );
hBuf.setLength( 0 );
cBuf.setLength( 0 );
}
}
len = cBuf.length();
if( len > 0 ){
while( len++ < cols ){
hBuf.append( " " );
cBuf.append( ' ' );
}
out.println( hBuf + " " + cBuf );
}
}
public void logStart( RecordStore rs ){
hBuf = new StringBuffer( cols * 3 );
cBuf = new StringBuffer( cols );
pBuf = new StringBuffer();
printChar( '=', cols * 4 + 1 );
numBytes = 0;
try {
out.println( "Record store: "
+ rs.getName() );
out.println( " Number of records = "
+ rs.getNumRecords() );
out.println( " Total size = "
+ rs.getSize() );
out.println( " Version = "
+ rs.getVersion() );
out.println( " Last modified = "
+ rs.getLastModified() );
out.println( " Size available = "
+ rs.getSizeAvailable() );
out.println( "" );
} catch( RecordStoreException e ){
logException( rs, e );</P< p>
}
}
private void printChar( char ch, int num ){
pBuf.setLength( 0 );
while( num-- > 0 ){
pBuf.append( ch );
}
out.println( pBuf.toString() );
}
}
// A logger that outputs to System.out.
public static class SystemLogger extends PrintStreamLogger
{
public SystemLogger(){
super( System.out );
}
public SystemLogger( int cols ){
super( System.out, cols );
}
}
RMS从入门到精通之四
[ 作者:mingjava 转贴自:本站原创 点击数:248 更新时间:2004-8-17 文章录入:admin ]
本文将主要讲述RecordFilter和RecordEnumeration的用法,这在整个Record Management System中都是至关重要的。由于本人觉得RecordComparator和RecordFilter类似并且用出相对小些,因此不再这里做介绍了。我们依然是通过一个示范的应用程序说明如何使用这两个接口。
RecordFilter的定义非常的简单,他只定义了一个方法那就是boolean matches(byte[] data)。
public interface RecordFilter {
boolean matches( byte[] recordData );
}
使用起来也非常简单,我们只需要实现这个方法并根据需要返回boolean类型的值就可以了,通常我们在查找纪录的时候使用这个接口,把它作为一个参数传递给enumerateRecords()方法,例如
RecordEnumeration records = rs.enumerateRecords(new RecordEntryFilter(key),null,false),首先我们看看这个方法的参数,第一个参数是RecordFilter,它就是用来筛选数据库中的纪录的,筛选的条件就是我们上面定义的方法,boolean matches(byte[] data),第二个参数是RecordComparator,它是对选择出来的数据进行排序的,如果你不指定的话就按照默认的顺序排序。第三个参数是boolean类型的,如果是true的话,那么record会跟踪rms中的数据变化,这是比较昂贵的开销,我一般都是用false的。得到records后我们可以进行很多有用的操作,具体的方法有:
public interface RecordEnumeration {
void destroy();
boolean hasNextElement();
boolean hasPreviousElement();
boolean isKeptUpdated();
void keepUpdated( boolean keepUpdated );
byte[] nextRecord() throws InvalidRecordIDException,
RecordStoreNotOpenException,
RecordStoreException;
int nextRecordId() throws InvalidRecordIDException;
int numRecords();
byte[] previousRecord() throws InvalidRecordIDException,
RecordStoreNotOpenException,
RecordStoreException;
int previousRecordId() throws InvalidRecordIDException;
void rebuild();
void reset();
}
其中标记的方法很常用应该记住。我们实现RecordFilter的时候通常是会写成一个类的内部类,这非常普遍也很合理。我下面的程序依然使用这样的方法,
private static class RecordEntryFilter implements RecordFilter
{
private String key;
public RecordEntryFilter(String key) {
this.key = key;
}
public boolean matches(byte[] data)
{
try
{
return RecordEntry.matches(data, key);
} catch (IOException e)
{
e.printStackTrace();
return false;
}
}
}
在例子用我们向RMS中存入几个数据实体,它包括一个username和一个phonenumber字段。我们写入和读出字段的时候可以使用系列之二中的序列化机制。
import java.io.*;
public class RecordEntry
{
private String userName;
private String phoneNum;
public RecordEntry() {
}
public RecordEntry(String userName, String phoneNum) {
this.userName = userName;
this.phoneNum = phoneNum;
}
public String getPhoneNum()
{
return phoneNum;
}
public void setPhoneNum(String phoneNum)
{
this.phoneNum = phoneNum;
}
public String getUserName()
{
return userName;
}
public void setUserName(String userName)
{
this.userName = userName;
}
public byte[] serialize() throws IOException
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
dos.writeUTF(userName);
dos.writeUTF(phoneNum);
return baos.toByteArray();
}
public static RecordEntry deserialize(byte[] data) throws IOException
{
ByteArrayInputStream bais = new ByteArrayInputStream(data);
DataInputStream dis = new DataInputStream(bais);
RecordEntry record = new RecordEntry();
record.userName = dis.readUTF();
record.phoneNum = dis.readUTF();
return record;
}
public static boolean matches(byte[] data,String key) throws IOException
{
ByteArrayInputStream bais = new ByteArrayInputStream(data);
DataInputStream dis = new DataInputStream(bais);
return dis.readUTF().equals(key);
}
public String toString()
{
return userName+":"+phoneNum;
}
}
我们本文要讲述的另一个重点是如何使用RecordEnumeration,相关的代码写在了一个RecordModel类里面如下:
import java.io.IOException;
import javax.microedition.rms.*;
public class RecordModel
{
private RecordStore rs;
private boolean firstTime = true;
public static final String NAME = "record";
private RecordEntry[] record = { new RecordEntry("ming", "12345"),
new RecordEntry("pain", "123456"),
new RecordEntry("linux", "234566"),
new RecordEntry("mingtian", "3456677") };
private static class RecordEntryFilter implements RecordFilter
{
private String key;
public RecordEntryFilter(String key) {
this.key = key;
}
public boolean matches(byte[] data)
{
try
{
return RecordEntry.matches(data, key);
} catch (IOException e)
{
e.printStackTrace();
return false;
}
}
}
public RecordModel() {
try
{
rs = RecordStore.openRecordStore(NAME, true);
} catch (RecordStoreException e)
{
e.printStackTrace();
}
if(firstTime)
{
init();
firstTime = false;
}
}
public void init()
{
try
{
for (int i = 0; i < record.length; i++)
{
byte[] data = record[i].serialize();
rs.addRecord(data, 0, data.length);</P< p>
}
} catch (IOException e)
{
e.printStackTrace();
} catch (RecordStoreException e)
{
e.printStackTrace();
}
}
public RecordEntry[] getRecord(String key)
{
try
{
if (rs.getNumRecords() > 0)
{
RecordEnumeration records = rs.enumerateRecords(
new RecordEntryFilter(key), null, false);
int length = records.numRecords();
if (length == 0)
{
return new RecordEntry[0];
} else
{
System.out.println(length);
RecordEntry[] record = new RecordEntry[length];
for (int i = 0; i < length; i++)
{
record[i] = RecordEntry.deserialize(rs
.getRecord(records.nextRecordId()));
}
return record;
}
} else
{
return new RecordEntry[0];
}
} catch (RecordStoreException e)
{
e.printStackTrace();
return new RecordEntry[0];
} catch (IOException e)
{
e.printStackTrace();
return new RecordEntry[0];
}
}
}
其中标记为灰色的代码是我们应该重点掌握的,下面是RecordFilterMIDlet类的代码:
import javax.microedition.lcdui.*;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;
public class RecordFilterMIDlet extends MIDlet implements ItemStateListener
{
private Display display;
private Form mainForm;
private TextField textField;
private RecordModel model;
protected void startApp() throws MIDletStateChangeException
{
display = Display.getDisplay(this);
mainForm = new Form("Test");
model = new RecordModel();
textField = new TextField("input:", null, 20, TextField.ANY);
mainForm.append(textField);
mainForm.setItemStateListener(this);
display.setCurrent(mainForm);
}
protected void pauseApp()
{
}
protected void destroyApp(boolean arg0) throws MIDletStateChangeException
{
}
public void itemStateChanged(Item item)
{
if (item == textField)
{
String input = textField.getString();
RecordEntry[] record = model.getRecord(input);
if (record.length == 0)
{
System.out.println("no record");
} else
{
for (int i = 0; i < record.length; i++)
{
System.out.println(record[i]);
}
}
}
}
}
为了方便我没有在手机界面上显示,而是输出到控制台。下面是在input内输入ming的时候在控制台打印出来的结果:
no record
no record
no record
1
ming:12345
你可能想把以输入字段为开头的记录筛选出来,就像在通讯录中查询一样。那么你可以在Filter中添加一个int类型的type来告诉实体中的matches()方法应该如何做。这样就可以筛选出你所需要的记录了:)
还是那句话,希望对大家有用!本来还想在系列5中介绍我自己实现的一个个人通信录,程序已经写完,大概有3k的代码吧,基本覆盖了前面所讲述的专题。由于我准备投稿,因此决定不继续写Record Management System的内容了。
本文地址:http://com.8s8s.com/it/it13996.htm