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

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

作者:Eric Giguere
2004 年 6 月

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

搜索策略

很明显,搜索一个特定记录或者记录集合的最简单方式是使用过滤器。该过滤器需要知道数据是如何存储在一个记录中的,因此您将希望在任何可能的情况下重用您的数据映射类。例如,在第 3 部分中我们定义 FieldListFieldBasedStore 类来处理读写任意数据到一个记录存储。进行一下重构,我们可以将不是特定于记录存储的代码移动到一个新的基类,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 大学计算机科学的学士和硕士学位,并广泛撰写有关计算主题的文章。

转载于:https://www.cnblogs.com/canpigfly/archive/2005/05/13/154443.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值