数据库和 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 大学计算机科学的学士和硕士学位,并广泛撰写有关计算主题的文章。
No comments:
Post a Comment