数据库和 MIDP,第 4 部分:过滤和遍历策略
2005-05-13 08:27
323 查看
数据库和 MIDP,第 4 部分:过滤和遍历策略
作者:Eric Giguere2004 年 6 月
在本系列文章的第 2 和第 3 部分,我们讨论在数据和字节数组之间相互映射的基本方式,从而在记录存储中存储它们,这些记录存储是通过记录管理系统(Record Management System,RMS)管理的。读写数据总是需要克服的第一个障碍,但是发现您所需要的数据也同样的重要,并且这样做的话,您需要能够浏览记录存储,以一种有用的方式排序记录,以及使用过滤器提取出想要的数据。本文探索执行这些任务的不同策略;第 5 部分将建立在这里所学知识的基础之上,并且向您展示如何搜索满足指定规则的记录。
记录 ID 并不是索引
为了读或写一条记录,您需要知道它的记录 ID。在第 1 部分中,您了解到记录 ID 是一个整数值,它唯一地标识在记录存储中的一条记录 - 并且它不是记录存储中的索引。这个差异具有某些重要的含义。如果具有 N 个记录的存储被索引,那么每条记录都将具有一个索引,其范围是 0 到 N-1 或者 1 到 N,这取决于范围是从 0 还是从 1 开始的。每当一条记录被删除或者插入的时候,在存储中的位于后面的记录索引将会相应的改变。该范围将会收缩或者增长,但是保持连续。
与索引不同的是,在记录存储中,无论在一条记录之前插入或者去除多少个其他的记录,该记录 ID 并不改变。增加到记录存储中的第一条记录将其记录 ID 赋值为 1,下一条记录 ID 为 2,等等。如果您删除一条记录,它的记录 ID 变为无效,并且任何访问该记录的企图都会抛出
InvalidRecordIDException。无效的记录 ID 不会保持连续。
由于它们唯一地标识记录,因此您可以通过将一个记录的 ID 存储为另一条记录中的数据值,从而使用记录 ID 将两个或者更多记录连接到一起。您还可以使用记录 ID 来同步数据和外部应用程序,正如您将在第 6 部分中所看到的。
记录 ID 的主要缺点是它们使记录存储遍历变得复杂化;您不能像在数组中那样,在一个索引集合中进行迭代。您必须使用两种遍历技术之一:强制或者枚举。
强制遍历
利用强制方法,您只是简单地逐个提取记录,从第一条记录开始,忽略无效的记录,一直继续直到您已经获取所有的记录:... RecordStore rs = ... // an open record store int lastID = rs.getNextRecordID(); int numRecords = rs.getNumRecords(); int count = 0; for( int id = 1; id < lastID && count < numRecords; ++id ){ try { byte[] data = rs.getRecord( id ); ... // process the data ++count; } catch( InvalidRecordIDException e ){ // just ignore and move to the next record } catch( RecordStoreException e ){ // a more general error that should be handled // somehow break; } } ... |
getNextRecordID()来发现将要增加到存储的下一条记录的 ID,并且将它用作可能的记录标识符的上界。该代码在每次读取到一个有效值时都增加计数器,因此一旦已经看到所有记录,就可以停止遍历。注意,在遍历期间,记录存储没有被锁定 —— 如果它对于您很重要的话,您将需要使用一个
synchronized块来防止其他线程改变该记录。
强制方法易于理解,并且如果缺失记录很少的话,将会工作得很好,但是首选的方法是使用枚举。
枚举记录
并不是检查每个记录 ID 以查看哪个记录是有效的,这本质上是强制方法所做的,您可以使用RecordEnumeration接口,请求 RMS 为您返回有效记录 ID 的一个枚举。该接口并没有扩展标准的
java.util.Enumeration接口,但是它以一种类似的方式工作。事实上,它实际上是一个更有能力的接口:您可以向后或者向前遍历一个记录枚举,并且当记录发生改变时可以使用它来跟踪变化。
您可以针对希望遍历的记录存储调用 enume
rateRecords()方法,从而获取一个枚举,正如在该实例中:
... RecordStore rs = ... // an open record store RecordEnumeration enum = rs.enumerateRecords( null, null, false ); ... // use the enumeration here enum.destroy(); // always clean it up! ... |
enumerateRecords()可以抛出一个
RecordStoreNotOpenException。
enumerateRecords()的前面两个参数控制如何过滤和排序记录 —— 我们将简要地讨论它们。第三个参数控制枚举是否跟踪记录存储的变化。跟踪变化要求额外的耗费,并且在大多数情况下并不需要,因此我们将在所有的实例中都将它设置为
false。
当您已经处理完一个枚举,您必须调用它的
destroy()方法,从而释放系统已经分配给它的任何资源 —— 记住,在 CLDC 中没有对象终止。未销毁的枚举将会导致您的应用程序泄漏内存。
使用
hasNextElement()and
nextRecordId()在枚举中向前移动:
... RecordStore rs = ... RecordEnumeration enum = ... try { while( enum.hasNextElement() ){ int id = enum.nextRecordId(); byte[] data = rs.getRecord( id ); ... // do something here } } catch( RecordStoreException e ){ // handle the error here } ... |
hasPreviousElement()和
previousRecordId()向后移动。注意,如果一条记录从记录存储中被删除,同时该枚举是活动的,那么
nextRecordId()和
previousRecordId()都将抛出
InvalidRecordIDException。
一个未排序枚举返回其记录的顺序是特定于实现,因此不要基于该顺序做出任何假设。
出于方便性考虑,您可以使用
nextRecord()或者
previousRecord()以获取后面或者前面记录的数据,而不是它的记录 ID:
... try { while( enum.hasNextElement() ){ byte[] data = enum.nextRecord(); ... // do something here } } catch( RecordStoreException e ){ // handle the error here } ... |
一个对于
numRecords()的调用将向您提供枚举中的记录 ID,但是该调用将导致该枚举立即具体化而不是递增地具体化,并且该操作可能使用大量内存或者在处理中导致一个显而易见的停顿。
使用
reset()将一个枚举重新设置为它的初始状态,使用
rebuild()强制它根据记录存储的当前状态更新它自身。
使用
isKeptUpdated()来检查一个枚举是否跟踪对于记录存储的变化,使用
keepUpdated()以改变它的跟踪状态。
过滤枚举记录
只是对于在记录存储中的数据子集感兴趣吗?您可以通过使用一个过滤器,从而具有一个忽略不必要记录的枚举,该过滤器实现RecordFilter接口。过滤器的
matches()方法应该指示是否一条记录将被包括在该枚举中:
public class MyFilter implements RecordFilter { public boolean matches( byte[] recordData ){ ... // matching code here } } |
enumerateRecords():
... enum = rs.enumerateRecords( new MyFilter(), null, false ); ... |
排序枚举记录
为了保证记录以一种一致的、可预测的次序被返回,您必须排序该枚举。您向该枚举提供一个比较器,这是一个实现RecordComparator接口的对象。比较器的
compare()方法返回一条记录是否先于、等于或者后于其他记录:
public class MyComparator implements RecordComparator { public int compare( byte[] r1, byte[] r2 ){ int salary1 = getSalary( r1 ); int salary2 = getSalary( r2 ); if( salary1 < salary2 ){ return PRECEDES; } else if( salary1 == salary2 ){ return EQUIVALENT; } else { return FOLLOWS; } } private int getSalary( byte[] data ){ ... // code to get salary info } } |
PRECEDES、
EQUIVALENT和
FOLLOWS的使用。这些常量是通过
RecordComparator接口定义的。为了使用一个比较器,将它作为第二个参数传递给
enumerateRecords():
... enum = rs.enumerateRecords( null, new MyComparator(), false ); ... |
当您创建枚举时,可以提供过滤器和比较器。如果您这样做的话,在数据被排序之前应用过滤器。
下一步工作
现在,我们知道如何移动记录存储,以及如何排序和过滤记录,您已经准备好学习第 5 部分,该部分将描述相关策略,用于搜索一条记录存储中符合特定准则的对象。关于作者
Eric Giguere 是 iAnywhere Solutions 的一名软件开发人员,iAnywhere Solutions 是 Sybase 的一个子公司,他在那里从事关于手持设备和无线计算的 Java 技术。他拥有 Waterloo 大学计算机科学的学士和硕士学位,并广泛撰写有关计算主题的文章。相关文章推荐
- 数据库部分备份策略
- 数据库和 MIDP,第 5 部分:搜索记录存储(ZT)
- 数据库笔记6(下半部分):检索 排序 过滤
- 数据库笔记6(下半部分):检索 排序 过滤
- 数据库笔记6(下半部分):检索 排序 过滤
- 数据库和 MIDP,第二部分:数据映射
- 数据库和 MIDP,第三部分:使用数据映射
- 数据库和 MIDP,第 5 部分:搜索记录存储
- 企业面试题库_数据库部分
- 使用SQLJ进行数据库开发 第二部分:SQLJ语言元素
- C#连接数据库部分占位符的使用实例
- 关系数据库的查询优化策略
- 探索推荐引擎内部的秘密,第 2 部分: 深入推荐引擎相关算法 - 协同过滤
- 流程设计器开发三(策略和命令部分)
- 常见端口详解及部分攻击策略
- Mootools 1.2教程 输入过滤第二部分(字符串)
- 使用c标签的forEach遍历展现数据库主表与从表的数据
- 小议数据库主键选取策略(转载)
- jQuery基础----20jQuery遍历之过滤
- 数据库分表实施策略