天天看點

J2ME 101,第 3 部分: 深入記錄管理系統

J2ME 101,第 3 部分: 深入記錄管理系統
釋出者:IBM   日期:2004-01-22 00:00:00 浏覽次數:0 (共有_條評論) 檢視評論 | 我要評論

級别: 初級

John Muchow ([email protected]), 自由技術作家/開發人員

2004 年  1 月  22 日

MIDP 不使用檔案系統儲存應用程式資料。相反,MIDP 使用一種名為記錄管理系統(Record Management System,RMS)的存儲系統把所有資訊都儲存在非易失性存儲器中。本文是            J2ME 101教程系列的補充系列的兩篇文章中的第一篇,作者和開發者 John Muchow 在此本文介紹了 RMS 應用程式接口的基礎知識,然後引導您完成幾個展示了它的功能的開發執行個體。       

資料存儲和檢索是應用程式開發的重要方面。存儲什麼樣的資料取決于應用程式的類型和複雜度。在某些情況下,被存儲的持久資料僅僅是應用程式使用者的首選項資訊。在另外一些情況下,您可能需要存儲和管理一個聯系資訊庫。在極端的情況下,您可能要為整個供應鍊資料設計一個資料存儲方案。不論是哪一種情況,對于支援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 位元組數組
... ...
J2ME 101,第 3 部分: 深入記錄管理系統

不要錯過本系列的其他文章 !

J2ME 101,第 1 部分:介紹 MIDP 的高層 UI(2003 年 12 月)分步介紹了推動使用者和裝置顯示之間主要互動的各個元件。               

J2ME 101,第 2 部分:介紹 MIDP 的低層 UI (2003 年 12 月) 介紹建立和使用 Canvas 和 Graphics 類的基礎知識,并概述了 MIDP 2.0 中引入的 Game API。               

惟一行辨別是一個整型值。第一個條目的 ID 為 1,然後是 2,以此類推。一行被删除之後,它的行辨別不會被重用。也就是說,如果表中有三行,ID分别是 1,2 和 3,删除 ID 2 将把這個辨別永久性地從記錄存儲中删除。如果我們向這個表中添加另外一行,那麼該行的辨別将會是 4。

記錄存儲是用名稱來辨別的。記錄存儲的名稱最多可以包含 32 個字元,并且所有字元都是大小寫敏感的。同一個 MIDlet 套件(即打包在一起的一個或多個MIDlet)中不會包含兩個名稱相同的記錄存儲。

每個記錄存儲都有一個版本号和一個日期/時間戳。在添加、替換或删除一個記錄時,這兩個值都會被更新。

J2ME 101,第 3 部分: 深入記錄管理系統
J2ME 101,第 3 部分: 深入記錄管理系統
J2ME 101,第 3 部分: 深入記錄管理系統
J2ME 101,第 3 部分: 深入記錄管理系統

建立一個記錄存儲

沒有用于建立記錄存儲的構造函數。相反,我們使用三個雙功能的方法來建立和/或打開記錄存儲。清單 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)
      
J2ME 101,第 3 部分: 深入記錄管理系統

删除一個記錄存儲

從裝置中删除一個 MIDlet 套件時,由這個套件建立的所有記錄存儲都将被删除。

如果指定的記錄存儲存在,第一個方法将會打開它。如果指定的記錄存儲不存在,并且參數                

createIfNecessary

被設為                

true

,該方法可以建立一個新的(指定名稱的)記錄存儲。第二個方法與第一個類似,隻不過使用另外兩個附加參數來指定記錄存儲的通路限制。第一個參數指定是否隻有位于相同套件中的MIDlet 才能通路這個記錄存儲。第二個參數指定有權通路記錄存儲的 MIDlet 是否可以建立一個新記錄。最後一個方法提供了讓 MIDlet打開在另外的 MIDlet 套件中的記錄存儲的方法。           
J2ME 101,第 3 部分: 深入記錄管理系統
J2ME 101,第 3 部分: 深入記錄管理系統
J2ME 101,第 3 部分: 深入記錄管理系統
J2ME 101,第 3 部分: 深入記錄管理系統

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)
      
J2ME 101,第 3 部分: 深入記錄管理系統

鎖定記錄

與傳統資料庫不同,RMS 沒有提供鎖定記錄存儲的方法。您需要借助裝置實作來保證所有操作是同步的。如果使用多個線程來通路一個記錄存儲,那麼您應該確定通路資源的線程不會互相影響。

通過後面幾節介紹的例子,您将會更多地了解                

RecordStore

API 和它的方法。            
J2ME 101,第 3 部分: 深入記錄管理系統
J2ME 101,第 3 部分: 深入記錄管理系統
J2ME 101,第 3 部分: 深入記錄管理系統
J2ME 101,第 3 部分: 深入記錄管理系統

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 的記錄輸出

J2ME 101,第 3 部分: 深入記錄管理系統
J2ME 101,第 3 部分: 深入記錄管理系統
J2ME 101,第 3 部分: 深入記錄管理系統
J2ME 101,第 3 部分: 深入記錄管理系統
J2ME 101,第 3 部分: 深入記錄管理系統

讀取和寫入 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 的輸出

J2ME 101,第 3 部分: 深入記錄管理系統
J2ME 101,第 3 部分: 深入記錄管理系統
J2ME 101,第 3 部分: 深入記錄管理系統
J2ME 101,第 3 部分: 深入記錄管理系統
J2ME 101,第 3 部分: 深入記錄管理系統

RecordEnumeration API

至此,我們已經使用一個簡單的                

for

循環周遊了記錄存儲。盡管這種技術已經足以滿足我們的需求,在這一節,我們還是會擴充自己的代碼,加入一個記錄枚舉器看看會有什麼結果。在代碼中加入一個記錄枚舉器的一個最大好處在于枚舉器中包含搜尋記錄存儲以及以排序的順序提取記錄的選項。           

清單 7 列出了 RecordEnumeration API 的方法。&#160

清單 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 來擷取記錄。           
J2ME 101,第 3 部分: 深入記錄管理系統

是否可以忽略更新?

  在建立枚舉器時,把                    

keepUpdated

參數設為                    

true

需要慎重考慮。盡管存儲發生改變時,記錄存儲結果集會更新,但連續的更新卻可能帶來性能瓶頸。               
J2ME 101,第 3 部分: 深入記錄管理系統
J2ME 101,第 3 部分: 深入記錄管理系統
J2ME 101,第 3 部分: 深入記錄管理系統
J2ME 101,第 3 部分: 深入記錄管理系統
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());
     ...
}
      
J2ME 101,第 3 部分: 深入記錄管理系統
J2ME 101,第 3 部分: 深入記錄管理系統
J2ME 101,第 3 部分: 深入記錄管理系統
J2ME 101,第 3 部分: 深入記錄管理系統

排序多種資料類型

顯然,對隻包含字元串資料的記錄進行排序非常簡單。然而,對每條記錄都包含不同資料類型的記錄存儲進行排序要複雜一些。例如,請回憶一下                 清單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. 根據整型值進行排序的記錄

J2ME 101,第 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();
       }
     ...
}
      
J2ME 101,第 3 部分: 深入記錄管理系統
J2ME 101,第 3 部分: 深入記錄管理系統
J2ME 101,第 3 部分: 深入記錄管理系統
J2ME 101,第 3 部分: 深入記錄管理系統

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()

方法來查找指定的搜尋字元串,并傳回一個表明成功還是失敗的布爾值。            
J2ME 101,第 3 部分: 深入記錄管理系統
J2ME 101,第 3 部分: 深入記錄管理系統
J2ME 101,第 3 部分: 深入記錄管理系統
J2ME 101,第 3 部分: 深入記錄管理系統

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. 記錄搜尋結果

J2ME 101,第 3 部分: 深入記錄管理系統
使用者界面的建立包括指定一個                

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;
     }
}
      
J2ME 101,第 3 部分: 深入記錄管理系統
J2ME 101,第 3 部分: 深入記錄管理系統
J2ME 101,第 3 部分: 深入記錄管理系統
J2ME 101,第 3 部分: 深入記錄管理系統
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,第 3 部分: 深入記錄管理系統
J2ME 101,第 3 部分: 深入記錄管理系統
J2ME 101,第 3 部分: 深入記錄管理系統
J2ME 101,第 3 部分: 深入記錄管理系統

結束語

在                 J2ME 101 系列的這第三篇文章中,您了解了如何在自己的 MIDP 應用程式中建立和管理資料記錄。文章開頭部分介紹的便利方法使您可以在記錄存儲中寫入和讀取記錄。                

RecordEnumeration

類使您可以在 RMS 的所有記錄之間移動。結合                

RecordComparator

時,它可以傳回排序的記錄,結合                

RecordEnumeration

類時它可以搜尋指定的記錄。如果将這三者 ―― 枚舉器、比較器和過濾器 ―― 結合在一起,您将可以查找特定的記錄,并按順序傳回結果。最後讨論的、但并非最不重要的API:                

RecordListener

可以用于為應用程式設定事件通知。           

幾周之後,我們将以對 MIDP中網絡支援的全面介紹來結束這個系列,請密切關注。