在本系列文章的第 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 大学计算机科学的学士和硕士学位,并广泛撰写有关计算主题的文章。
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/374079/viewspace-131806/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/374079/viewspace-131806/