您的位置:首页 > 数据库

数据库和 MIDP,第 5 部分:搜索记录存储

2005-05-13 08:27 375 查看

数据库和 MIDP,第 5 部分:搜索记录存储

作者:Eric Giguere

2004 年 6 月

在本系列文章的第 4 部分中,您学会如何遍历一个记录存储,按照有用的次序排序记录,以及使用过滤器选择期望的记录。本文探索各种用于发现符合指定准则的一个或多个记录的策略。

搜索策略

很明显,搜索一个特定记录或者记录集合的最简单方式是使用过滤器。该过滤器需要知道数据是如何存储在一个记录中的,因此您将希望在任何可能的情况下重用您的数据映射类。例如,在第 3 部分中我们定义
FieldList
FieldBasedStore
类来处理读写任意数据到一个记录存储。进行一下重构,我们可以将不是特定于记录存储的代码移动到一个新的基类,
FieldBasedRecordMapper


package j2me.rms;

import java.io.*;
import javax.microedition.rms.*;
import j2me.io.*;

// A base class for writing and reading arbitrary
// data as defined by a FieldList

public abstract class FieldBasedRecordMapper {

// Some useful constants

public static Boolean TRUE = new Boolean( true );
public static Boolean FALSE = new Boolean( false );

// Markers for the types of string we support

private static final byte NULL_STRING_MARKER = 0;
private static final byte UTF_STRING_MARKER = 1;

// Constructs the mapper for the given list

protected FieldBasedRecordMapper(){
}

// Prepares for input by setting the data buffer.

protected void prepareForInput( byte[] data ){
if( _bin == null ){
_bin = new DirectByteArrayInputStream( data );
_din = new DataInputStream( _bin );
} else {
_bin.setByteArray( data );
}
}

// Prepares the store for output. The streams are reused.

protected void prepareForOutput(){
if( _bout == null ){
_bout = new DirectByteArrayOutputStream();
_dout = new DataOutputStream( _bout );
} else {
_bout.reset();
}
}

// Reads a field from the buffer.

protected Object readField( int type ) throws IOException {
switch( type ){
case FieldList.TYPE_BOOLEAN:
return _din.readBoolean() ? TRUE : FALSE;
case FieldList.TYPE_BYTE:
return new Byte( _din.readByte() );
case FieldList.TYPE_CHAR:
return new Character( _din.readChar() );
case FieldList.TYPE_SHORT:
return new Short( _din.readShort() );
case FieldList.TYPE_INT:
return new Integer( _din.readInt() );
case FieldList.TYPE_LONG:
return new Long( _din.readLong() );
case FieldList.TYPE_STRING: {
byte marker = _din.readByte();
if( marker == UTF_STRING_MARKER ){
return _din.readUTF();
}
}
}

return null;
}

// Converts an object to a boolean value.

public static boolean toBoolean( Object value ){
if( value instanceof Boolean ){
return ((Boolean) value).booleanValue();
} else if( value != null ){
String str = value.toString().trim();

if( str.equals( "true" ) ) return true;
if( str.equals( "false" ) ) return false;

return( toInt( value ) != 0 );
}

return false;
}

// Converts an object to a char.

public static char toChar( Object value ){
if( value instanceof Character ){
return ((Character) value).charValue();
} else if( value != null ){
String s = value.toString();
if( s.length() > 0 ){
return s.charAt( 0 );
}
}

return 0;
}

// Converts an object to an int. This code
// would be much simpler if the CLDC supported
// the java.lang.Number class.

public static int toInt( Object value ){
if( value instanceof Integer ){
return ((Integer) value).intValue();
} else if( value instanceof Boolean ){
return ((Boolean) value).booleanValue() ? 1 : 0;
} else if( value instanceof Byte ){
return ((Byte) value).byteValue();
} else if( value instanceof Character ){
return ((Character) value).charValue();
} else if( value instanceof Short ){
return ((Short) value).shortValue();
} else if( value instanceof Long ){
return (int) ((Long) value).longValue();
} else if( value != null ){
try {
return Integer.parseInt( value.toString() );
}
catch( NumberFormatException e ){
}
}

return 0;
}

// Converts an object to a long. This code
// would be much simpler if the CLDC supported
// the java.lang.Number class.

public static long toLong( Object value ){
if( value instanceof Integer ){
return ((Integer) value).longValue();
} else if( value instanceof Boolean ){
return ((Boolean) value).booleanValue() ? 1 : 0;
} else if( value instanceof Byte ){
return ((Byte) value).byteValue();
} else if( value instanceof Character ){
return ((Character) value).charValue();
} else if( value instanceof Short ){
return ((Short) value).shortValue();
} else if( value instanceof Long ){
return ((Long) value).longValue();
} else if( value != null ){
try {
return Long.parseLong( value.toString() );
}
catch( NumberFormatException e ){
}
}

return 0;
}

// Writes a field to the output buffer.

protected void writeField( int type, Object value )
throws IOException {
switch( type ){
case FieldList.TYPE_BOOLEAN:
_dout.writeBoolean( toBoolean( value ) );
break;
case FieldList.TYPE_BYTE:
_dout.write( (byte) toInt( value ) );
break;
case FieldList.TYPE_CHAR:
_dout.writeChar( toChar( value ) );
break;
case FieldList.TYPE_SHORT:
_dout.writeShort( (short) toInt( value ) );
break;
case FieldList.TYPE_INT:
_dout.writeInt( toInt( value ) );
break;
case FieldList.TYPE_LONG:
_dout.writeLong( toLong( value ) );
break;
case FieldList.TYPE_STRING:
if( value != null ){
String str = value.toString();
_dout.writeByte( UTF_STRING_MARKER );
_dout.writeUTF( str );
} else {
_dout.writeByte( NULL_STRING_MARKER );
}
break;
}
}

// Writes a set of fields to the output stream.

protected byte[] writeStream( FieldList list,
Object[] fields )
throws IOException {
int count = list.getFieldCount();
int len = ( fields != null ? fields.length : 0 );

prepareForOutput();

for( int i = 0; i < count; ++i ){
writeField( list.getFieldType( i ),
( i < len ? fields[i] : null ) );
}

return _bout.getByteArray();
}

private DirectByteArrayInputStream  _bin;
private DirectByteArrayOutputStream _bout;
private DataInputStream             _din;
private DataOutputStream            _dout;
}

现在,我们扩展
FieldBasedRecordMapper
以创建另一个抽象类,
FieldBasedFilter
,这是我们过滤器的基类:

package j2me.rms;

import javax.microedition.rms.*;

// A record filter for filtering records whose data
// is mapped to a field list. The actual filter will
// extend this class and implement the matchFields
// method appropriately.

public abstract class FieldBasedFilter
extends FieldBasedRecordMapper
implements RecordFilter {

// Constructs the filter. The optional byte
// array is an array that we want ignored,
// usually the first record in the record store
// where we store the field information.

protected FieldBasedFilter(){
this( null );
}

protected FieldBasedFilter( byte[] ignore ){
_ignore = ignore;
}

// Compares two byte arrays.

private boolean equal( byte[] a1, byte[] a2 ){
int len = a1.length;

if( len != a2.length ) return false;

for( int i = 0; i < len; ++i ){
if( a1[i] != a2[i] ) return false;
}

return true;
}

// Called to filter a record.

public boolean matches( byte[] data ){
if( _ignore != null ){
if( equal( _ignore, data ) ) return false;
}

prepareForInput( data );
return matchFields();
}

// The actual filter implements this method.

protected abstract boolean matchFields();

private byte[] _ignore;
}

假设我们这样定义一个如下的
FieldList
实例:

...
FieldList empFields = new FieldList( 5 );

empFields.setFieldType( 0, FieldList.TYPE_INT );
empFields.setFieldName( 0, "ID" );
empFields.setFieldType( 1, FieldList.TYPE_STRING );
empFields.setFieldName( 1, "Given Name" );
empFields.setFieldType( 2, FieldList.TYPE_STRING );
empFields.setFieldName( 2, "Last Name" );
empFields.setFieldType( 3, FieldList.TYPE_BOOLEAN );
empFields.setFieldName( 3, "Active" );
empFields.setFieldType( 4, FieldList.TYPE_CHAR );
empFields.setFieldName( 4, "Sex" );
...

一个匹配特定姓氏的过滤器看起来类似于:

package j2me.rms;

import java.io.IOException;

// A filter that matches a specific last name
// in an employee record.

public class MatchLastName extends FieldBasedFilter {
public MatchLastName( String name ){
this( name, null );
}

public MatchLastName( String name, byte[] ignore ){
super( ignore );
_name = name;
}

protected boolean matchFields(){
try {
readField( FieldList.TYPE_INT );
readField( FieldList.TYPE_STRING );

String ln = (String)
readField( FieldList.TYPE_STRING );

return ln.equals( _name );
}
catch( IOException e ){
return false;
}
}

private String _name;
}

发现匹配的记录是一件简单的事情:

...
RecordStore employees = ... // list of employees
RecordFilter lname = new MatchLastName( "Smith" );
RecordEnumeration enum =
employees.enumerateRecords( lname, null, false );

while( enum.hasNextElement() ){
int id = enum.nextRecordId();
... // etc. etc./
}

enum.destroy();
...

仔细地编码,您可以避免打开任何不需要的记录。您可以采取的一个方法是在内存中缓存最近访问的记录。例如,每当过滤器匹配一个记录时,打开该记录并且将它的已打开形式存储到缓存中,这种打开形式通常是一个表示单一实体的对象,例如雇员。将它的记录 ID 用作关键字 —— 当然,您需要在记录中存储 ID。当您遍历该枚举时,在访问底层记录存储之前检查已打开记录的缓存。

实际上,使用枚举方式将匹配记录收集和打开为单独的列表可能更为简单。考虑我们在第 2 部分中定义的 Contact 类,它的简单的
toByteArray()
fromByteArray()
方法用于在实例和字节数组之间进行映射:

package j2me.example;

import java.io.*;

// The contact information for a person

public class Contact {
private String _firstName;
private String _lastName;
private String _phoneNumber;

public Contact(){
}

public Contact( String firstName, String lastName,
String phoneNumber )
{
_firstName = firstName;
_lastName = lastName;
_phoneNumber = phoneNumber;
}

public String getFirstName(){
return _firstName != null ? _firstName : "";
}

public String getLastName(){
return _lastName != null ? _lastName : "";
}

public String getPhoneNumber(){
return _phoneNumber != null ? _phoneNumber : "";
}

public void setFirstName( String name ){
_firstName = name;
}

public void setLastName( String name ){
_lastName = name;
}

public void setPhoneNumber( String number ){
_phoneNumber = number;
}

public void fromByteArray( byte[] data )
throws IOException {
ByteArrayInputStream bin =
new ByteArrayInputStream( data );
DataInputStream din = new DataInputStream( bin );

fromDataStream( din );
din.close();
}

public byte[] toByteArray() throws IOException {
ByteArrayOutputStream bout =
new ByteArrayOutputStream();
DataOutputStream dout = new DataOutputStream( bout );

toDataStream( dout );
dout.close();

return bout.toByteArray();
}

public void fromDataStream( DataInputStream din )
throws IOException {
_firstName = din.readUTF();
_lastName = din.readUTF();
_phoneNumber = din.readUTF();
}

public void toDataStream( DataOutputStream dout )
throws IOException {
dout.writeUTF( getFirstName() );
dout.writeUTF( getLastName() );
dout.writeUTF( getPhoneNumber() );
}
}

下面的类使用记录过滤器作为一种发现和存储匹配记录的方式。它返回
false
以指示一个空的枚举,从而立即被丢弃:

package j2me.example;

import java.io.*;
import java.util.*;
import javax.microedition.rms.*;

// Finds the contacts whose first and/or last
// names match the given values.

public class FindContacts {

// Constructs the finder for the given names. If
// both names are non-null, both names must match,
// otherwise only the given name needs to match.

public FindContacts( String fname, String lname ){
_fname = normalize( fname );
_lname = normalize( lname );
}

// Traverses the data in the record store and
// returns a list of matching Contact objects.

public Vector list( RecordStore rs )
throws RecordStoreException,
IOException {

Vector v = new Vector();
Filter f = new Filter( v );
RecordEnumeration enum =
rs.enumerateRecords( f, null, false );

// The enum will never have any elements in it,
// but we call this to force it to traverse
// its list.

enum.hasNextElement();
enum.destroy();

return v;
}

// Returns whether or not a given Contact
// instance matches our criteria.

public boolean matchesContact( Contact c ){
boolean sameFirst = false;
boolean sameLast = false;

if( _fname != null ){
sameFirst =
c.getFirstName().toLowerCase().equals(_fname);
}

if( _lname != null ){
sameLast =
c.getLastName().toLowerCase().equals( _lname );
}

if( _fname != null && _lname != null ){
return sameFirst && sameLast;
}

return sameFirst || sameLast;
}

// Normalize our name data

private static String normalize( String name ){
return( name != null ?
name.trim().toLowerCase() : null );
}

private String _fname;
private String _lname;

// A record filter that always returns false but
// whenever it finds a matching contact it adds it
// to the given list.

private class Filter implements RecordFilter {
private Filter( Vector list ){
_list = list;
}

public boolean matches( byte[] data ){
try {
Contact c = new Contact();
c.fromByteArray( data );

if( matchesContact( c ) ){
_list.addElement( c );
}
}
catch( IOException e ){
}

return false;
}

private Vector _list;
}
}

不幸的是,内存限制可能阻止每次缓存更多的对象。您可以通过使用索引搜索一张单独维护的表,从而获得某些性能。这个表将记录 ID 与关键字值例如联系名相配对。一个索引通常足够的小,以便于将它保持在内存中,并且省去您每次需要发现特定记录时都要使用枚举的麻烦。

 

关于作者

Eric Giguere 是 iAnywhere Solutions 的一名软件开发人员,iAnywhere Solutions 是 Sybase 的一个子公司,他在那里从事关于手持设备和无线计算的 Java 技术。他拥有 Waterloo 大学计算机科学的学士和硕士学位,并广泛撰写有关计算主题的文章。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐