數據存儲和檢索是應用程序開發的重要方面。存儲什麼樣的數據取決於應用程序的類型和復雜度。在某些情況下,被存儲的持久數據僅僅是應用程序用戶的首選項信息。在另外一些情況下,您可能需要存儲和管理一個聯系信息庫。在極端的情況下,您可能要為整個供應鏈數據設計一個數據存儲方案。不論是哪一種情況,對於支持 MIDP 的設備來說只有一種通用的數據存儲和管理方案,那就是記錄管理系統 (RMS)。
作為 J2ME 101 教程系列的補充系列兩篇文章中的第一篇,本文將向您介紹 MIDP 持久存儲系統的內部工作原理。我們將從 RMS 的概覽開始,不過大部分內容(如同教程系列一樣)更傾向於實際應用。並且,我們將會構建一些 MIDlet,幫助您了解如何從 RMS 中讀取和寫入數據記錄,並了解這個功能非常全面而緊湊的數據管理系統的各種排序、搜索和檢索選項。
注意,本文假設您熟悉 J2ME 環境中的 MIDlet 開發。為了編譯代碼示例,系統上需要安裝 J2ME 開發環境。請參閱 參考資料 部分,其中有 J2ME Wireless Toolkit(WTK)安裝指南的超鏈接。
MIDP 中的記錄管理
簡單地說,MIDP 記錄管理系統(RMS)提供了一種跨多個 MIDlet 調用持久性地存儲應用程序數據的手段。您可以把 RMS 記錄存儲看作一個簡單的數據庫,其中每一行都包括兩列:一列用於存儲惟一的行標識,另外一列存儲代表記錄中數據的一組字節。表 1 展示了一個簡單的記錄存儲數據庫。
表 1. 一個記錄存儲數據庫
記錄 ID 數據 1 字節數組 2 字節數組 3 字節數組 ... ...
惟一行標識是一個整型值。第一個條目的 ID 為 1,然後是 2,以此類推。一行被刪除之後,它的行標識不會被重用。也就是說,如果表中有三行,ID 分別是 1,2 和 3,刪除 ID 2 將把這個標識永久性地從記錄存儲中刪除。如果我們向這個表中添加另外一行,那麼該行的標識將會是 4。
記錄存儲是用名稱來標識的。記錄存儲的名稱最多可以包含 32 個字符,並且所有字符都是大小寫敏感的。同一個 MIDlet 套件(即打包在一起的一個或多個 MIDlet)中不會包含兩個名稱相同的記錄存儲。
每個記錄存儲都有一個版本號和一個日期/時間戳。在添加、替換或刪除一個記錄時,這兩個值都會被更新。
創建一個記錄存儲
沒有用於創建記錄存儲的構造函數。相反,我們使用三個雙功能的方法來創建和/或打開記錄存儲。清單 1 列出了這三個方法。
清單 1. 創建和打開記錄存儲
RecordStore openRecordStore(String recordStoreName,
boolean createIfNecessary)
RecordStore openRecordStore(String recordStoreName,
boolean createIfNecessary,
int authmode,
boolean writable)
RecordStore openRecordStore(String recordStoreName,
String vendorName,
String suiteName)
如果指定的記錄存儲存在,第一個方法將會打開它。如果指定的記錄存儲不存在,並且參數 createIfNecessary 被設為 true ,該方法可以創建一個新的(指定名稱的)記錄存儲。第二個方法與第一個類似,只不過使用另外兩個附加參數來指定記錄存儲的訪問限制。第一個參數指定是否只有位於相同套件中的 MIDlet 才能訪問這個記錄存儲。第二個參數指定有權訪問記錄存儲的 MIDlet 是否可以創建一個新記錄。最後一個方法提供了讓 MIDlet 打開在另外的 MIDlet 套件中的記錄存儲的方法。
RecordStore API
有多個操縱記錄存儲的方法可供使用。涉及的范圍從添加、刪除、替換記錄內容到枚舉整個記錄存儲。清單 2 列出了所有可用的方法。
清單 2. 記錄存儲方法
void closeRecordStore()
void deleteRecordStore(String recordStoreName)
String[] listRecordStores()
int addRecord(byte[] data, int offset, int numBytes)
void setRecord(int recordId, byte[] newData, int offset, int numBytes)
void deleteRecord (int recordId)
byte[] getRecord (int recordId)
int getRecord (int recordId, byte[] buffer, int offset)
int getRecordSize (int recordId)
int getNextRecordID()
int getNumRecords()
long getLastModifIEd()
int getVersion()
String getName()
int getSize()
int getSizeAvailable()
RecordEnumeration enumerateRecords(RecordFilter filter,
RecordComparator comparator,
boolean keepUpdated)
void addRecordListener (RecordListener listener)
void removeRecordListener (RecordListener listener)
void setMode(int authmode, boolean writable)
通過後面幾節介紹的例子,您將會更多地了解 RecordStore API 和它的方法。
ReadWrite MIDlet
我們的第一個展示例子是 ReadWrite MIDlet。這個 MIDlet 的功能包括:創建記錄存儲、把多條記錄寫入持久存儲中、讀取這些記錄、退出時刪除記錄存儲。在查看下面的代碼時,請注意這個 MIDlet 包含了幾個“便利”方法,這些方法在使用 RMS 的過程中被反復使用。便利方法就是那些用來打開、關閉和刪除記錄存儲的方法。
請閱讀 ReadWrite MIDlet的完整代碼,下面我們將詳細討論它。
關於代碼的說明
在繼續討論之前有幾點值得一提。首先,就像前面解釋過的,在這個例子中我們在調用 RecordStore.openRecordStore(REC_STORE, true) 時, createIfNecessary 參數傳遞的是 true 以創建一個新的記錄存儲。
其次,當使用 writeRecord(String str) 向記錄存儲中寫入數據時,我們首先把 Java 字符串參數轉換成一個字節數組。然後把這個字節數組傳遞給 addRecord(rec, 0, rec.length) 方法以向記錄存儲中插入一條記錄。
最後,在 readRecords() 方法中,我們分配了一個字節數組用於存儲從 RMS 中讀取的記錄數據。這需要我們在每次讀取時都進行一個特定的檢查,以保證數組的長度足夠容納數據。提取記錄以後,我們就可以把內容輸出到控制台。
圖 1 展示了 ReadWrite MIDlet 在 J2ME WTK 中運行時得到的輸出。
圖 1. ReadWrite MIDlet 的記錄輸出
讀取和寫入 Java 基本類型
ReadWrite MIDlet 只能將文本字符串寫入到記錄存儲中。在接下來的例子中,我們將增加存儲和操縱整型、布爾型和字符串值的功能。我們還將添對加用 Java 流進行讀寫的支持。同前面的例子一樣,我們將會寫入幾條記錄,然後再從存儲中將它們讀出。
像所有需要訪問記錄存儲的 MIDlet 一樣,我們首先使用 openRecordStore() 方法分配和打開(或創建)一個記錄存儲,如清單 3 所示。
清單 3. 創建一個記錄存儲
private RecordStore rs = null; // Record store
...
public void openRecStore()
{
...
// Create record store if it does not exist
rs = RecordStore.openRecordStore(REC_STORE, true);
...
}
清單 4 顯示了我們將會寫入到記錄存儲中的基本數據類型。
清單 4. Java 基本數據類型
public void writeTestData()
{
boolean[] booleans = {true,false};
int[] integers = {17 , 4};
String[] strings = {"Golf", "Tennis"};
writeStream(booleans, integers, strings);
}
如果熟悉 Java 語言中流的用法,您會發現使用 MIDlet 與使用更傳統的 Java 應用程序稍有不同。在 MIDlet 中使用流的步驟如下:
分配流。
寫入數據。
清空流。
把流數據轉移到數組中。
把數組寫入到記錄存儲。
關閉流。
清單 5 展示了如何使用流向 RMS 中寫入數據:
清單 5. 把流寫入到記錄存儲
public void writeStream(boolean[] bData, int[] iData, String[] sData)
{
try
{
// Write data into an internal byte array
ByteArrayOutputStream strmBytes = new ByteArrayOutputStream();
// Write Java data types into the above byte array
DataOutputStream strmDataType = new DataOutputStream(strmBytes);
byte[] record;
for (int i = 0; i < sData.length; i++)
{
// Write Java data types
strmDataType.writeBoolean(bData[i]);
strmDataType.writeInt(iData[i]);
strmDataType.writeUTF(sData[i]);
// Clear any buffered data
strmDataType.flush();
// Get stream data into byte array and write record
record = strmBytes.toByteArray();
rs.addRecord(record, 0, record.length);
// Toss any data in the internal array so writes
// starts at beginning (of the internal array)
strmBytes.reset();
}
strmBytes.close();
strmDataType.close();
}
catch (Exception e)
{
db(e.toString());
}
}
然後,我們要從記錄存儲中讀取數據。首先創建必要的輸入流,然後遍歷所有記錄,把其中的內容存儲到一個字節數組。通過訪問數據輸入流,我們從字節數組中讀取每一個 Java 基本類型,並把相應的內容輸出到控制台。如清單 6 所示。
清單 6. 從記錄存儲中讀取流
public void readStream()
{
try
{
// Allocate space to hold each record
byte[] recData = new byte[50];
// Read from the specifIEd byte array
ByteArrayInputStream strmBytes = new ByteArrayInputStream(recData);
// Read Java data types from the above byte array
DataInputStream strmDataType = new DataInputStream(strmBytes);
for (int i = 1; i <= rs.getNumRecords(); i++)
{
// Get data into the byte array
rs.getRecord(i, recData, 0);
// Read back the data types
System.out.println("Record #" + i);
System.out.println("Boolean: " + strmDataType.readBoolean());
System.out.println("Integer: " + strmDataType.readInt());
System.out.println("String: " + strmDataType.readUTF());
System.out.println("--------------------");
// Reset so read starts at beginning of array
strmBytes.reset();
}
strmBytes.close();
strmDataType.close();
}
catch (Exception e)
{
db(e.toString());
}
}
現在檢查 ReadWritePrimitives MIDlet的源代碼。請仔細研究,如果願意的話,您也可以在自己的 WTK 模擬器中運行這段代碼以查看輸出。
ReadWritePrimitives MIDlet 的控制台輸出如圖 2 所示。
圖 2. ReadWritePrimitives MIDlet 的輸出
RecordEnumeration API
至此,我們已經使用一個簡單的 for 循環遍歷了記錄存儲。盡管這種技術已經足以滿足我們的需求,在這一節,我們還是會擴展自己的代碼,加入一個記錄枚舉器看看會有什麼結果。在代碼中加入一個記錄枚舉器的一個最大好處在於枚舉器中包含搜索記錄存儲以及以排序的順序提取記錄的選項。
清單 7 列出了 RecordEnumeration API 的方法。 
清單 7. RecordEnumeration API
int numRecords()
byte[] nextRecord()
int nextRecordId()
byte[] previousRecord()
int previousRecordId()
boolean hasNextElement()
boolean hASPreviousElement()
void keepUpdated(boolean keepUpdated)
boolean isKeptUpdated()
void rebuild()
void reset()
void destroy()
為代碼添加枚舉功能非常簡單,只需在一個打開的記錄存儲上創建一個 RecordEnumeration 對象並遍歷所有記錄即可。清單 8 展示了如何創建一個 RecordEnumeration 。前面兩個參數(在本例中都是 null )指定在記錄存儲上進行搜索和/或排序的類。第三個參數是 API 中定義的 keepUpdated 標志。如果這個變量設置為 true ,當記錄存儲發生改變時,枚舉結果集將被重新初始化。將這個參數設置為 false 則指定枚舉忽略記錄存儲的更新。
清單 8. 創建一個 RecordEnumeration
rs = RecordStore.openRecordStore(REC_STORE, true);
...
RecordEnumeration re = rs.enumerateRecords(null, null, false);
while (re.hasNextElement())
{
// Get next record
String str = new String(re.nextRecord());
...
}
為什麼需要記錄枚舉
乍一看,枚舉循環提供的功能與 for 循環提供的沒什麼不同,但實際上它提供了多得多的功能。除了提供一種搜索和排序的手段(我們馬上將會看到),記錄枚舉還克服了 for 循環的最大弊端(盡管不是一眼就能看出來)。
本文開頭部分曾經提到記錄 ID 不能在記錄存儲中被重用。因此,如果一個存儲有三條記錄,ID 分別為 1,2 和 3,當 ID 2 被刪除時,存儲中就只剩下 ID 1 和 3。如果不去考慮我們是如何遍歷記錄存儲的,那麼這不會是一個問題。清單 9 中的代碼將改變您的想法。
清單 9. 一個讀取記錄的典型 for 循環
byte[] recData = new byte[5];
int len;
for (int i = 1; i <= rs.getNumRecords(); i++)
{
// Allocate more storage if necessary
if (rs.getRecordSize(i) > recData.length)
recData = new byte[rs.getRecordSize(i)];
len = rs.getRecord(i, recData, 0);
}
您可能已經發現了,代碼的最後一行將會帶來問題。盡管 getNumRecords() 可以返回正確的記錄數(兩條),但是 for 循環返回的變量 i 的值將會是 1 和 2。但是在存儲中的記錄 ID 是 1 和 3。當 i 等於 2 時對 rs.getRecord() 的調用將會失敗,因為已經沒有記錄 ID 為 2 的記錄了。這正是記錄枚舉可以發揮作用的地方,因為它不是根據 ID 來獲取記錄。
RecordComparator API
RecordEnumeration API 提供了使用枚舉的基礎,使得我們可以遍歷 RMS 中所有條目。真正讓我們可以按順序從記錄存儲中提取數據的是 RecordComparator 接口。
RecordComparator API 包括一個方法和三個預定義的返回值。該接口中惟一的方法接收兩個參數,它們都是字節數組。在清單 10 中,您可以了解到這兩個數組如何表示來自記錄存儲的記錄。枚舉調用 compare() 方法,把來自存儲的兩個記錄的內容作為參數傳遞給該方法。枚舉和我們的搜索代碼將遍歷整個記錄存儲,根據指定的搜索條件構建一個排序過的結果集。
清單 10. RecordComparator API
int compare(byte[] rec1, byte[] rec2)
static int EQUIVALENT
static int FOLLOWS
static int PRECEDES
清單 11 展示了一個實現了 Comparator 接口的簡單類,它通過比較每條記錄的字符串內容對記錄進行排序。注意,返回值必須是預定義的值: EQUIVALENT , PRECEDES 或 FOLLOWS 。緊跟 comparator 類之後的代碼創建了一個比較器的實例,並在創建記錄枚舉時將其作為一個參數。
清單 11. 一個用於排序的 Comparator 類
//********************************************************
// Create comparator class for sorting
//********************************************************
public class comparator implements RecordComparator
{
public int compare(byte[] rec1, byte[] rec2)
{
String str1 = new String(rec1), str2 = new String(rec2);
int result = str1.compareTo(str2);
if (result == 0)
return RecordComparator.EQUIVALENT;
else if (result < 0)
return RecordComparator.PRECEDES;
else
return RecordComparator.FOLLOWS;
}
}
...
//********************************************************
// How to Access the comparator using a record enumeration
//
// Note: Variable 'rs' is created outside the scope of
// this method
//********************************************************
// Create a new comparator for sorting
comparator comp = new comparator();
// Reference the comparator when creating the result set
RecordEnumeration re = rs.enumerateRecords(null, comp, false);
// RetrIEve each record in sorted order
while (re.hasNextElement())
{
String str = new String(re.nextRecord());
...
}
排序多種數據類型
顯然,對只包含字符串數據的記錄進行排序非常簡單。然而,對每條記錄都包含不同數據類型的記錄存儲進行排序要復雜一些。例如,請回憶一下 清單 4,在這個例子中我們在每條記錄中寫入三種基本 Java 數據類型,它們分別是 boolean、integer 和 string。怎樣才能根據其中特定的一種類型進行排序呢?為了做到這一點,比較器函數需要獲取用於比較的適當字段,執行比較,然後向枚舉器返回適當的值( EQUIVALENT , PRECEDES 或 FOLLOWS )。
為了更好地理解這一過程,我們來編寫一個根據整型進行排序的 MIDlet。首先,我們將更新 清單 4 中的 writeTestData() 方法,使記錄存儲中的每條記錄既包含字符串數據,也包含整型數據,如下所示:
清單 12. 用於排序的 Java 基本數據類型
public void writeTestData()
{
String[] strings = {"Java", "J2ME", "C"};
int[] integers = {2, 1, 3};
writeStream(strings, integers);
}
IntegerSort MIDlet 的輸出如圖 3 所示。注意,比較器函數(我們馬上將會講到)返回根據每條記錄中整型值排序之後的多條記錄。
圖 3. 根據整型值進行排序的記錄
構建一個比較器
IntegerSort MIDlet 的代碼(請參閱 完整的源代碼)與前面的 MIDlet 在整體上差別不大。最大的改變是添加了 comparator 類(清單 13)以及抽取適當字段並進行實際排序的方法。以下是為 IntegerSort MIDlet 處理所有細節的 ComparatorInteger 類。
清單 13. ComparatorInteger 類
/*--------------------------------------------------
* Compares two integers to determine sort order
* Each record passed in contains multiple Java data
* types - use only the integer data for sorting
*-------------------------------------------------*/
class ComparatorInteger implements RecordComparator
{
private byte[] recData = new byte[10];
// Read from a specifIEd byte array
private ByteArrayInputStream strmBytes = null;
// Read Java data types from the above byte array
private DataInputStream strmDataType = null;
public void compareIntClose()
{
try
{
if (strmBytes != null)
strmBytes.close();
if (strmDataType != null)
strmDataType.close();
}
catch (Exception e)
{}
}
public int compare(byte[] rec1, byte[] rec2)
{
int x1, x2;
try
{
// If either record is larger than our buffer, reallocate
int maxsize = Math.max(rec1.length, rec2.length);
if (maxsize > recData.length)
recData = new byte[maxsize];
// Read record #1
// We want the integer from the record, which is
// the second "fIEld" thus we must read the
// String first to get to the integer value
strmBytes = new ByteArrayInputStream(rec1);
strmDataType = new DataInputStream(strmBytes);
strmDataType.readUTF(); // Read string
x1 = strmDataType.readInt(); // Read integer
// Read record #2
strmBytes = new ByteArrayInputStream(rec2);
strmDataType = new DataInputStream(strmBytes);
strmDataType.readUTF(); // Read string
x2 = strmDataType.readInt(); // Read integer
// Compare record #1 and #2
if (x1 == x2)
return RecordComparator.EQUIVALENT;
else if (x1 < x2)
return RecordComparator.PRECEDES;
else
return RecordComparator.FOLLOWS;
}
catch (Exception e)
{
return RecordComparator.EQUIVALENT;
}
}
請注意讀取 Java 基本類型的代碼。我們需要從每條記錄中提取出整型值,這個值是每條記錄的第二個“字段”。因此,我們只是讀取字符串(UTF)值並將它丟到一邊。第二次讀取把整型值存儲在一個本地變量中(x1 或 x2)。接著,比較這些值以確定正確的排序順序。
清單 14. 讀取並排序 Java 基本類型
// Read record #1
...
strmDataType.readUTF(); // Read string
x1 = strmDataType.readInt(); // Read integer
// Read record #2
...
strmDataType.readUTF(); // Read string
x2 = strmDataType.readInt(); // Read integer
// Compare record #1 and #2
if (x1 == x2)
return RecordComparator.EQUIVALENT;
else if (x1 < x2)
return RecordComparator.PRECEDES;
else
return RecordComparator.FOLLOWS;
構建一個枚舉器
完成比較器的編寫之後,我們來創建一個枚舉器,其中引用 了 ComparatorInteger 類的一個實例。這個枚舉器將使用比較器作為排序算法,從記錄存儲創建一個記錄結果集。清單 15 列出了 readStream() 方法的一部分,該方法創建比較器和枚舉器,遍歷整個結果集並把記錄內容顯示在控制台上。
清單 15. readStream() 方法
public void readStream()
{
...
if (rs.getNumRecords() > 0)
{
// Create instance of the comparator
ComparatorInteger comp = new ComparatorInteger();
// Create enumerator, referencing the comparator
RecordEnumeration re = rs.enumerateRecords(null, comp, false);
// Loop through all elements in the result set
int i = 1;
while (re.hasNextElement())
{
// Get data into the byte array
rs.getRecord(re.nextRecordId(), recData, 0);
// Read back the data types
System.out.println("Record #" + i++);
System.out.println("String: " + strmDataType.readUTF());
System.out.println("Integer: " + strmDataType.readInt());
System.out.println("--------------------");
// Reset so read starts at beginning of array
strmBytes.reset();
}
...
}
RecordFilter API
使用比較器進行排序是使用枚舉器時的一種選擇,另外一種選擇是使用過濾器進行搜索。比較器和過濾器之間的一個微小差別在於 比較器 返回排序後的整個記錄存儲,而 過濾器 只返回那些滿足指定條件的記錄。如果同時使用比較器和過濾器,那麼將會按照排序順序返回滿足搜索條件的記錄。
與 RecordComparator 類似, RecordFilter 也是通過在枚舉器代碼中增加一個方法 matches() 實現的。枚舉器對存儲中的每條記錄調用 matches() 方法。根據返回的布爾值,每條記錄要麼成為結果集的成員,要麼被歸為不滿足搜索條件的記錄放棄。
清單 16. RecordFilter API 的 matches() 方法
boolean matches(byte[] candidate)
構建一個過濾器
清單 17 展示了一個實現了 RecordFilter 接口的類。注意,搜索字符串被指定為 SearchFilter 構造函數的一個參數。這個字符串被保存在私有變量中,這樣當枚舉器調用 matches() 方法創建結果集的時候就可以訪問這個字符串。在這個例子中,搜索字符串還被轉換成小寫字符,因此這個搜索 不是大小寫敏感的。
清單 17. 構建一個 RecordFilter
//********************************************************
// Create filter class for searching
//********************************************************
class SearchFilter implements RecordFilter
{
private String searchText = null;
public SearchFilter(String searchText)
{
// Text to find
this.searchText = searchText.toLowerCase();
}
public boolean matches(byte[] candidate)
{
String str = new String(candidate).toLowerCase();
// Does the text exist?
if (searchText != null && str.indexOf(searchText) != -1)
return true;
else
return false;
}
}
...
//********************************************************
// How to Access the filter using a record enumeration
//
// Note: Variable 'rs' is created outside the scope of
// this method
//********************************************************
// Create search filter
SearchFilter search = new SearchFilter("abc");
// Reference filter when creating the result set
RecordEnumeration re = rs.enumerateRecords(search, null, false);
// If there is at least one record in result set, a match was found
if (re.numRecords() > 0)
{
// At least one record in the result set, do something here...
...
}
注意,本例中執行實際搜索的代碼非常簡單。我們使用 Java 字符串的 indexOf() 方法來查找指定的搜索字符串,並返回一個表明成功還是失敗的布爾值。
StringSearch MIDlet
我們將構建最後一個 MIDlet 來展示到目前已經學過的內容(這裡是 StringSearch MIDlet 的完整源代碼)。除了可以在 RMS 中搜索記錄,這個 MIDlet 還加強了用戶界面。這一次,我們不再只使用控制台顯示輸出,還將顯示一個提示用戶輸入文本字符串的 TextFIEld 組件(您應該可以想起教程系列中的這部分內容!)。提出請求後,我們將在記錄存儲中搜索這個字符串。所有匹配的字符串都添加到顯示中以顯示搜索結果。
清單 18 展示了將被寫入到記錄存儲中的搜索字符串。
清單 18. 記錄存儲中的條目
public void writeTestData()
{
String[] strs = {
"I think this would be a good time for a beer. (FDR)",
"I'll make it a felony to drink small beer. (Shakespeare)",
"They who drink beer will think beer. (Washington Irving)",
"I would give all my fame for a pot of ale. (Shakespeare)"};
writeRecords(strs);
}
圖 4 展示了有兩個不同搜索結果的 MIDlet。
圖 4. 記錄搜索結果
用戶界面的創建包括指定一個 Form 、一個 TextFIEld 和兩個 Command ―― 一個用於搜索記錄存儲,另外一個用於退出 MIDlet,如清單 19 所示。
清單 19. 創建用戶界面組件
...
// Define textfIEld, stringItem and commands
tfFind = new TextField("Find", "", 12, TextFIEld.ANY);
cmExit = new Command("Exit", Command.EXIT, 1);
cmFind = new Command("Find", Command.SCREEN, 2);
// Create the form, add commands
fmMain = new Form("Record Search");
fmMain.addCommand(cmExit);
fmMain.addCommand(cmFind);
// Append textfIEld and stringItem to form
fmMain.append(tfFind);
...
事件處理
一旦 StringSearch MIDlet 處於活動狀態,所有事件都將在 commandAction() 方法中處理。請求 cmFind 命令時,程序將會調用 searchRecordStore() 開始搜索處理。這個過程包括指派一個 SearchFilter() 類的實例,並把這個實例關聯到記錄枚舉對象。指派了枚舉對象後,它將會調用搜索函數來搜索記錄存儲。所有與搜索字符串相匹配的記錄都將成為枚舉結果集的一部分。
清單 20 展示了處理事件以及搜索與用戶輸入文本字符串相匹配的記錄的代碼。請注意創建搜索過濾器時對 tfFind.getString() 的引用,該調用從文本字段中獲取用戶輸入的搜索字符串。然後,字符串被保存在 SearchFilter 類的變量 searchText 中。當枚舉器調用方法 matches() 時――對記錄存儲中的每條記錄調用一次――它將在當前的活動記錄中搜索匹配 searchText 的字符串。
清單 20. 處理用戶事件
public void commandAction(Command c, Displayable s)
{
if (c == cmFind)
{
searchRecordStore();
}
else if (c == cmExit)
{
destroyApp(false);
notifyDestroyed();
}
}
...
//********************************************************
// Search the record store
//********************************************************
private void searchRecordStore()
{
try
{
// Record store is not empty
if (rs.getNumRecords() > 0)
{
// Setup the search filter with the user requested text
SearchFilter search = new SearchFilter(tfFind.getString());
RecordEnumeration re = rs.enumerateRecords(search, null, false);
// Remove any previous record entrIEs displayed on the form
clearForm();
// A match was found using the filter
if (re.numRecords() > 0)
{
// Append all records found onto the form
while (re.hasNextElement())
fmMain.append(new String(re.nextRecord()));
}
re.destroy(); // Release enumerator
}
}
catch (Exception e)
{
db(e.toString());
}
}
...
//********************************************************
// Called for each record when creating the enumerator.
// Checks to see if the record contains text that
// matches the text string entered by the user.
//********************************************************
class SearchFilter implements RecordFilter
{
private String searchText = null;
public SearchFilter(String searchText)
{
// Text to find
this.searchText = searchText.toLowerCase();
}
public boolean matches(byte[] candidate)
{
String str = new String(candidate).toLowerCase();
// Look for text
if (searchText != null && str.indexOf(searchText) != -1)
return true;
else
return false;
}
}
RecordListener API
RecordListener 接口是我們討論的最後一個 API,但並不表明它是最不重要的。在代碼中實現 RecordListener 可以保證當記錄存儲修改、添加或刪除的時候您可以得到通知。
以下是使用 RecordListener 的基本步驟:
打開(創建)一個記錄存儲。
創建一個新的監聽器。
實現 RecordListener 接口中的所有方法。
RecordListener API 中的所有方法的傳入參數都相同:一個指向發生修改的記錄存儲的引用和受到影響的記錄 ID。清單 21 顯示了 RecordListener API 的方法。
清單 21. RecordListener API
void recordAdded(RecordStore recordStore, int recordId)
void recordChanged(RecordStore recordStore, int recordId)
void recordDeleted(RecordStore recordStore, int recordId)
清單 22 中的一小段代碼展示了如何在 RMS 的記錄發生添加、刪除或更改的時候把消息顯示在控制台上。
清單 22. 創建一個記錄監聽器
// Open record store
rs = RecordStore.openRecordStore(REC_STORE, true);
...
// Using handle to open record store, create a listener
rs.addRecordListener(new DemoRecordListener());
...
//********************************************************
// Listener to process updates to the record store
//********************************************************
class DemoRecordListener implements RecordListener
{
public void recordAdded(RecordStore recordStore, int recordId)
{
System.out.println("Record added");
}
public void recordDeleted(RecordStore recordStore, int recordId)
{
System.out.println("Record deleted");
}
public void recordChanged(RecordStore recordStore, int recordId)
{
System.out.println("Record changed");
}
結束語
在 J2ME 101 系列的這第三篇文章中,您了解了如何在自己的 MIDP 應用程序中創建和管理數據記錄。文章開頭部分介紹的便利方法使您可以在記錄存儲中寫入和讀取記錄。 RecordEnumeration 類使您可以在 RMS 的所有記錄之間移動。結合 RecordComparator 時,它可以返回排序的記錄,結合 RecordEnumeration 類時它可以搜索指定的記錄。如果將這三者 ―― 枚舉器、比較器和過濾器 ―― 結合在一起,您將可以查找特定的記錄,並按順序返回結果。最後討論的、但並非最不重要的 API: RecordListener 可以用於為應用程序設置事件通知。
幾周之後,我們將以對 MIDP 中網絡支持的全面介紹來結束這個系列,請密切關注。