4

我們即將開始開發新的移動應用程序。這個特定的應用程序將用於大量搜索基於文本的字段。來自整個團隊的任何建議是哪種數據庫引擎最適合在移動平臺上進行這些類型的搜索?在移動設備上進行全文搜索?

細節包括Windows Mobile 6,我們將使用.Net CF.此外,一些基於文本的字段將在35到500個字符之間。該設備將以兩種不同的方式運行,即批量和WiFi。當然,對於WiFi,我們可以將請求提交給一個完整的數據庫引擎,並將結果返回。這個問題圍繞着「批處理」版本,該版本將存儲裝載有設備閃存/可移動存儲卡信息的數據庫。

無論如何,我知道SQLCE有一些基本的索引,但是你沒有深入到真正的花哨的「全文」風格索引,直到獲得完整版本,當然這在移動設備上不可用平臺。

的數據是什麼樣子的一個例子:

「圍裙木匠調節真皮容器室腰硬件帶」等等,等等

我還沒有得到到任何其他的評價具體的選擇,但我想我會利用這個組的經驗,以便首先指出我的一些具體的途徑。

任何建議/提示?

+0

仍在尋找這一個一些答案。 – 2008-11-22 02:12:20

回答

5

就在最近,我有同樣的問題。這是我做的:

我創建了一個類,只保存一個id和每個對象的文本(在我的情況下,我稱之爲sku(項目號)和說明)。這會創建一個使用較少內存的較小對象,因爲它僅用於搜索。找到匹配後,我仍然會從數據庫中抓取完整的對象。

public class SmallItem 
{ 
    private int _sku; 
    public int Sku 
    { 
     get { return _sku; } 
     set { _sku = value; } 
    } 

    // Size of max description size + 1 for null terminator. 
    private char[] _description = new char[36]; 
    public char[] Description 
    { 
     get { return _description; } 
     set { _description = value; } 
    } 

    public SmallItem() 
    { 
    } 
} 

創建這個類之後,您就可以創建一個數組(實際上我使用了我的情況的列表)這些對象,並用它爲整個應用程序進行搜索。這個列表的初始化需要一些時間,但你只需要在啓動時擔心這個問題。基本上只需對數據庫運行查詢並獲取創建此列表所需的數據。

一旦你有一個列表,你可以快速通過它搜索任何你想要的單詞。由於它是一個包含,它還必須在單詞內找到單詞(例如,鑽頭會返回鑽頭,鑽頭,鑽頭等)。爲此,我們編寫了一個自制的,非託管的c#包含函數。它需要一個字符串數組(因此您可以搜索多個單詞......我們將它用於「AND」搜索...描述必須包含所有傳入的單詞......「OR」目前不受支持在這個例子中)。當它搜索單詞列表時,它會建立一個ID列表,然後傳回給調用函數。一旦你有了一個ID列表,你可以很容易地在你的數據庫中運行一個快速查詢,以基於快速索引的ID號返回成熟的對象。我應該提到,我們也限制了返回結果的最大數量。這可以被取出。如果某人輸入諸如「e」之類的東西作爲他們的搜索字詞,這很方便。這將返回很多結果。

這裏是習俗的例子包含功能:

public static int[] Contains(string[] descriptionTerms, int maxResults, List<SmallItem> itemList) 
{ 
    // Don't allow more than the maximum allowable results constant.    
    int[] matchingSkus = new int[maxResults]; 

    // Indexes and counters. 
    int matchNumber = 0; 
    int currentWord = 0; 
    int totalWords = descriptionTerms.Count() - 1; // - 1 because it will be used with 0 based array indexes 

    bool matchedWord; 

    try 
    { 
     /* Character array of character arrays. Each array is a word we want to match. 
     * We need the + 1 because totalWords had - 1 (We are setting a size/length here, 
     * so it is not 0 based... we used - 1 on totalWords because it is used for 0 
     * based index referencing.) 
     * */ 
     char[][] allWordsToMatch = new char[totalWords + 1][]; 

     // Character array to hold the current word to match. 
     char[] wordToMatch = new char[36]; // Max allowable word size + null terminator... I just picked 36 to be consistent with max description size. 

     // Loop through the original string array or words to match and create the character arrays. 
     for (currentWord = 0; currentWord <= totalWords; currentWord++) 
     { 
      char[] desc = new char[descriptionTerms[currentWord].Length + 1]; 
      Array.Copy(descriptionTerms[currentWord].ToUpper().ToCharArray(), desc, descriptionTerms[currentWord].Length); 
      allWordsToMatch[currentWord] = desc; 
     } 

     // Offsets for description and filter(word to match) pointers. 
     int descriptionOffset = 0, filterOffset = 0; 

     // Loop through the list of items trying to find matching words. 
     foreach (SmallItem i in itemList) 
     { 
      // If we have reached our maximum allowable matches, we should stop searching and just return the results. 
      if (matchNumber == maxResults) 
       break; 

      // Loop through the "words to match" filter list. 
      for (currentWord = 0; currentWord <= totalWords; currentWord++) 
      { 
       // Reset our match flag and current word to match. 
       matchedWord = false; 
       wordToMatch = allWordsToMatch[currentWord]; 

       // Delving into unmanaged code for SCREAMING performance ;) 
       unsafe 
       { 
        // Pointer to the description of the current item on the list (starting at first char). 
        fixed (char* pdesc = &i.Description[0]) 
        { 
         // Pointer to the current word we are trying to match (starting at first char). 
         fixed (char* pfilter = &wordToMatch[0]) 
         { 
          // Reset the description offset. 
          descriptionOffset = 0; 

          // Continue our search on the current word until we hit a null terminator for the char array. 
          while (*(pdesc + descriptionOffset) != '\0') 
          { 
           // We've matched the first character of the word we're trying to match. 
           if (*(pdesc + descriptionOffset) == *pfilter) 
           { 
            // Reset the filter offset. 
              filterOffset = 0; 

            /* Keep moving the offsets together while we have consecutive character matches. Once we hit a non-match 
            * or a null terminator, we need to jump out of this loop. 
            * */ 
            while (*(pfilter + filterOffset) != '\0' && *(pfilter + filterOffset) == *(pdesc + descriptionOffset)) 
            { 
             // Increase the offsets together to the next character. 
             ++filterOffset; 
             ++descriptionOffset; 
            } 

            // We hit matches all the way to the null terminator. The entire word was a match. 
            if (*(pfilter + filterOffset) == '\0') 
            { 
             // If our current word matched is the last word on the match list, we have matched all words. 
             if (currentWord == totalWords) 
             { 
              // Add the sku as a match. 
              matchingSkus[matchNumber] = i.Sku.ToString(); 
              matchNumber++; 

              /* Break out of this item description. We have matched all needed words and can move to 
              * the next item. 
              * */ 
              break; 
             } 

             /* We've matched a word, but still have more words left in our list of words to match. 
             * Set our match flag to true, which will mean we continue continue to search for the 
             * next word on the list. 
             * */ 
             matchedWord = true; 
            } 
           } 

           // No match on the current character. Move to next one. 
           descriptionOffset++; 
          } 

          /* The current word had no match, so no sense in looking for the rest of the words. Break to the 
          * next item description. 
          * */ 
          if (!matchedWord) 
           break; 
         } 
        } 
       } 
      } 
     }; 

     // We have our list of matching skus. We'll resize the array and pass it back. 
     Array.Resize(ref matchingSkus, matchNumber); 
     return matchingSkus; 
    } 
    catch (Exception ex) 
    { 
     // Handle the exception 
    } 
} 

一旦你有匹配的SKU列表中,您可以通過遍歷數組,並建立一個查詢命令只返回匹配的SKU。

出於性能方面的想法,這裏是我們發現(執行以下步驟):

  • 搜索〜171000個項目
  • 創建所有符合條件的物品
  • 查詢數據庫,只能返回列表匹配項目
  • 構建成熟項目(與SmallItem類相似,但有更多字段)
  • 用全面吹響的項目對象填充數據網格。

在我們的移動設備上,整個過程需要2-4秒(如果我們在搜索所有項目之前達到了匹配限制,則需要2秒;如果我們必須掃描每個項目,則需要4秒)。

我也試過這樣做,沒有非託管的代碼,並使用String.IndexOf(並試圖String.Contains ...具有與IndexOf應該相同的性能)。這種方式慢得多......約25秒。

我也嘗試過使用StreamReader和包含[Sku Number] | [Description]行的文件。該代碼與非託管代碼示例類似。整個掃描過程大約需要15秒。速度不是太糟糕,但不是很好。該文件和StreamReader方法比我展示給你的方式有一個優點。該文件可以提前創建。我向你展示的方式需要內存和初始時間來在應用程序啓動時加載列表。對於我們的171,000個項目,這需要大約2分鐘。如果您每次啓動應用程序時都可以等待初始加載(這可以在單獨的線程上完成),那麼以這種方式搜索是最快的方法(至少我已經找到)。

希望有所幫助。

PS - 感謝Dolch幫助處理一些非託管代碼。

2

你可以試試Lucene.Net。我不確定它是否適合移動設備,但它被稱爲「高性能,全功能的文本搜索引擎庫」。

http://incubator.apache.org/lucene.net/ http://lucene.apache.org/java/docs/

+0

感謝您的建議。我會看看它,看看到C#的端口是什麼。我唯一擔心的是它現在看起來不活躍,因爲該項目是2007年4月的最後一個主要版本。 – 2008-11-10 13:01:25