探索J2ME:对记录进行排序

RecordStore类提供了对J2ME记录库的基本的访问功能。然而,lsMain显示的开销项目按照输入顺序排列产生了一个小问题,即查询某一个项目比较麻烦。
  
  在本文中,我将介绍RMS的记录排序API——特别是RecordEnumeration类和 RecordComparator接口,你可以在javax.microedition.rms软件包中找到这两者。我同时还要顺便谈谈RecordFilter接口,它可以让你在记录库中查找某个特殊的记录。你可以在这儿下载最新版本ExpensesApp的代码。
  
  用RecordComparator排序
  在代码清单 A中,你会发现ExpenseInfo.LoadExpenses又一次被修改了,这次利用RecordEnumeration对象来按照记录所保存的某项数据,而不是记录插入的顺序,来从记录库中查找记录:
  RecordEnumeration enu = rs.enumarateRecords(null, new ExpenseComparator, false);
  
  RecordStore.enumerateRecords接受一个ExpenseComparator类的对象(为参数),ExpenseComparator类实现了RecordComparator的接口,RecordEnumeration用它来确定用来排序的记录的顺序。我在清单B中给出了ExpenseComparator的代码。
  
  让我们来检测一下RecordComparator.compare方法。Compare方法用于处理两个记录,这两个记录均处于字节数组的形式(参数为bytes和bytes1数组),并且必须可以从中提取出任何可以决定先后次序的数据(决定先后次序的方式由RecordEnumeration确定)。该方法然后这样指出这两个记录的相对关系::
  
  如果由bytes代表的记录(插入时间)在前,那么compare会返回ExpenseComparator.PRECEDES,并且bytes在bytes1之前出现在枚举(enumeration)中。
  如果bytes所代表的记录(的插入时间)在 bytes1所代表的记录之后,那么compare返回ExpenseComparator.FOLLOWS,这样,byteIs在bytes之前出现在枚举中。
  如果这两个记录是等价的(equivalent)(即同一天输入的),compare返回ExpenseComparator.EQUIVALENT,这两个记录的顺序任意。
  在ExpenseComparator中,我从这两个记录中获取ExpenseDate字段(它以“当前时刻的毫秒数”的格式被存到记录库中)并根据这两个记录的排序返回相应的值。
  
  实现RecordComparator时,要记住对记录库中的每一个记录至少要调用一次compare方法,(当枚举开始产生时)。所以,你需要正确引导比较过程,使得这所费时间尽量的短,以免没有必要地降低应用程序的运行速度。还有一点就是,你用于比较的两个记录在最后无需紧联(immediately adjacent),分类枚举。
  使用记录枚举
  
  你也许还记得我在上一篇文章中抱怨使用RecordStore类是如何让人感到沮丧、而它“名不副实”(相对于RMS API中的其它类)的方法又有多少。再次强调,获得MIDP类API的JavaDoc文档将会对你的J2ME工作特别有用。
  
  再次看看清单A中的代码,你会注意到现在记录提取循环(*译者注:用于提取、读取记录的循环*)是用RecordEnumeration.hasNextElement方法做为控制变量的。在以前,每一个开销项目的ID号保存在ExpenseInfo的一个实例中,记录中的数据——开销日期、说明、美元数、美分数(*译者注:在前面已经说过,J2ME的变量没有浮点型,所以花费用美元和美分这两个整数来表示。*)、归类——是通过两个流读取类来按顺序提取的。
  
  联合使用RecordEnumeration和xpenseComparator使得lsMain中的开销项目按项目中的数据的顺序来显示,这就比按照开销项目插入的顺序显得更加符合逻辑。然而,即使你并没有打算对记录进行排序,你也应该考虑用RecordEnumeration来在记录库中查询记录。这样做比使用RecordStore类更加简单,而且还回避了使用RecordStore类的几个潜在问题,如我在上一篇文章结尾所提到的删除bug。
  
  棘手的记录指针
  
  当你留心这些不一致的方法名称时,它们就仅仅是一件令人讨厌的事而已;但是除此之外,还存在一个更大的、会令你寝食难安的问题。当你偶尔粗略浏览文档后,你可能会想到,仅有nextRecord方法和previousRecord方法可以在枚举下移动记录指针。进一步思考后,你会发现并情况不是这样的,另一个方法也可以操作记录指针,这就是nextRecordId。
  
  你必须清楚这个事实,因为当你希望更新一个记录之前,获知它的ID号几乎是不可避免的。这样,下面的查询记录的方法或许会派上用场:
  
  调用 nextRecordId来获取枚举中下一条记录的ID号。
  用nextRecord方法得到下一条记录。
  获取该记录中的数据。
  重复上述过程,直到最后一条记录。
  上述方法的问题是:由于调用nextRecordId也会移动记录指针,当你刚刚查询完一半记录是,你就捕获到一个莫名其妙的InvalidRecordIdException例外而不得不重新开始(to boot)。
  
  正如你在代码清单A中所见,解决第一个问题的方法是用RecordStore.getRecord来取代RecordEnumeration.nextRecord检索记录。我知道,这个方法并不完善;但是至少可以工作。那个让人摸不着头脑的例外是在记录指针指到枚举的最后一个记录的情况下调用nextRecordId而产生,所以你在写代码时要注意避免这种情况。
  用RecordFilter来查找记录
  
  
  尽管我没有在ExpensesApp应用程序中完成它,RecordEnumeration也可能完成搜寻记录的功能。为了做到这一点,你要向RecordStore.enumerateRecords传递一个类(该类实现了RecordFilter的接口)的实例,并完全忽略RecordComparator。RecordFilter仅有一个名为matches方法,它接受一个字节数组参数(字节数组代表了某个记录)。该方法用于检测记录,并根据被检测的记录是否符合预定标准而返回“真”或者“假”。
  
  举例来说,假设我们有一个RecordFilter的实现:ExpenseFilter,它在整个记录库中搜寻开销记录中的“开销分类(category)”字段符合ExpenseInfo.CATEGORYMEALS的开销项目,如代码清单C所示。为了获得只有符合上述条件的记录集合(enumeration,枚举),我可以这样组织代码:
  RecordStore rs = RecordStore.openRecordStore(RS_NAME);
  RecordEnumeration enu = es.enumerateRecords(new ExpenseFilter, null, false);
  
  在这里,变量enu仅包含分类为“膳食”(这在用户界面指点)的开销记录。
  
  工作尚未完成,我们仍需努力
  
  到目前为止,ExpensesApp已经相当完善了。现在,它已经有了个像样的用户界面(包括添加新的开销记录的快捷方式),也有了些实际用处——在运行过程中存储信息。但是仍有一些问题:
  
  新添加的开销记录并没有按照排序插入链表中的对应位置。
  ExpenseInfo的实例中的任何改变仅仅更新内存,而没有考虑到如果该实例已经存盘,还需要将更新后的内容重新存盘——我担心这一点可能还没有人看出来。
  本应用程序还不适合在移动设备运行,因为当它暂停运行时,它没有试图释放它所占用的资源——而这一点是移动设备应用程序所必须考虑的。