Lucene.net 系列二 --- index (上)

本文繼續系列一詳細介紹了有關Lucene.net索引添加刪除更新的詳細內容.並給出了所有的TestCase供學習參考.
Lucene建立Index的過程:
1.        抽取文本.
比如將PDF以及Word中的內容以純文本的形式提取出來.Lucene所支持的類型主要爲String,爲了方便同時也支持Date 以及Reader.其實如果使用這兩個類型lucene會自動進行類型轉換.
2.        文本分析.
Lucene將針對所給的文本進行一些最基本的分析,並從中去除一些不必要的信息,比如一些常用字a ,an, the 等等,如果搜索的時候不在乎字母的大小寫, 又可以去掉一些不必要的信息.總而言之你可以把這個過程想象成一個文本的過濾器,所有的文本內容通過分析, 將過濾掉一些內容,剩下最有用的信息.
3.        寫入index.
和google等常用的索引技術一樣lucene在寫index的時候都是採用的倒排索引技術(inverted index.) 簡而言之,就是通過某種方法(類似hash表?)將常見的”一篇文檔中含有哪些詞”這個問題轉成”哪篇文檔中有這些詞”. 而各個搜索引擎的索引機制的不同主要在於如何爲這張倒排表添加更準確的描述.比如google有名的PageRank因素.Lucene當然也有自己的技術,希望在以後的文章中能爲大家加以介紹.
在上一篇文章中,使用了最基本的建立索引的方法.在這裏將對某些問題加以詳細的討論.
1. 添加Document至索引
上次添加的每份文檔的信息是一樣的,都是文檔的filenamecontents.
doc.Add(Field.Keyword("filename", file.FullName));  
doc.Add(Field.Text("contents", new StreamReader(file.FullName)));
Lucene中對每個文檔的描述是可以不同的,比如,兩份文檔都是描述一個人,其中一個添加的是name, age 另一個添加的是id, sex ,這種不規則的文檔描述在Lucene中是允許的.
還有一點Lucene支持對Field進行Append , 如下:
string baseWord = "fast";
string synonyms[] = String {"quick", "rapid", "speedy"};
Document doc = new Document();
doc.Add(Field.Text("word", baseWord));
for (int i = 0; i < synonyms.length; i++)
     doc.Add(Field.Text("word", synonyms[i]));
這點純粹是爲了方便用戶的使用.在內部Lucene自動做了轉化,效果和將它們拼接好再存是一樣.
2. 刪除索引中的文檔
這一點Lucene所採取的方式比較怪,它使用IndexReader來對要刪除的項進行標記,然後在Reader Close的時候一起刪除.
這裏簡要介紹幾個方法.
[TestFixture]

public class DocumentDeleteTest : BaseIndexingTestCase   // BaseIndexingTestCase中的SetUp方法                                                   //建
立了索引其中加入了兩個Document
{

     [Test]
     public void testDeleteBeforeIndexMerge()
     {      


         IndexReader reader = IndexReader.Open(dir); //當前索引中有兩個Document

         Assert.AreEqual(2, reader.MaxDoc());   //文檔從0開始計數,MaxDoc表示下一個文檔的序號

         Assert.AreEqual(2, reader.NumDocs()); //NumDocs表示當前索引中文檔的個數  

         reader.Delete(1);                    //對標號爲1的文檔標記爲待刪除,邏輯刪除

         Assert.IsTrue(reader.IsDeleted(1));         //檢測某個序號的文檔是否被標記刪除

         Assert.IsTrue(reader.HasDeletions());       //檢測索引中是否有Document被標記刪除

         Assert.AreEqual(2, reader.MaxDoc());        //當前下一個文檔序號仍然爲2

         Assert.AreEqual(1, reader.NumDocs());       //當前索引中文檔數變成1

         reader.Close();                             //此時真正從物理上刪除之前被標記的文檔

         reader = IndexReader.Open(dir);

         Assert.AreEqual(2, reader.MaxDoc());         

         Assert.AreEqual(1, reader.NumDocs());

         reader.Close();

     }

     [Test]
     public void DeleteAfterIndexMerge()            //在索引重排之後
     {

         IndexReader reader = IndexReader.Open(dir);

         Assert.AreEqual(2, reader.MaxDoc());

         Assert.AreEqual(2, reader.NumDocs());

         reader.Delete(1);

         reader.Close();

         IndexWriter writer = new IndexWriter(dir, GetAnalyzer(), false);

         writer.Optimize();                          //索引重排

         writer.Close();

         reader = IndexReader.Open(dir);

         Assert.IsFalse(reader.IsDeleted(1));

         Assert.IsFalse(reader.HasDeletions());

         Assert.AreEqual(1, reader.MaxDoc());       //索引重排後,下一個文檔序號變爲1

         Assert.AreEqual(1, reader.NumDocs());

         reader.Close();

     }
}
當然你也可以不通過文檔序號進行刪除工作.採用下面的方法,可以從索引中刪除包含特定的內容文檔.
IndexReader reader = IndexReader.Open(dir);
reader.Delete(new Term("city", "Amsterdam"));
reader.Close();
你還可以通過reader.UndeleteAll()這個方法取消前面所做的標記,即在read.Close()調用之前取消所有的刪除工作.
3. 更新索引中的文檔
這個功能Lucene沒有支持, 只有通過刪除後在添加來實現. 看看代碼,很好理解的.
[TestFixture]
public class DocumentUpdateTest : BaseIndexingTestCase
{
     [Test]
     public void Update()
     {
         Assert.AreEqual(1, GetHitCount("city", "Amsterdam"));

         IndexReader reader = IndexReader.Open(dir);

         reader.Delete(new Term("city", "Amsterdam"));

         reader.Close();

         Assert.AreEqual(0, GetHitCount("city", "Amsterdam"));

         IndexWriter writer = new IndexWriter(dir, GetAnalyzer(),false);

         Document doc = new Document();

         doc.Add(Field.Keyword("id", "1"));

         doc.Add(Field.UnIndexed("country", "Netherlands"));

         doc.Add(Field.UnStored("contents","Amsterdam has lots of bridges"));

         doc.Add(Field.Text("city", "Haag"));

         writer.AddDocument(doc);

         writer.Optimize();

         writer.Close();

         Assert.AreEqual(1, GetHitCount("city", "Haag"));
     }

  
     protected override Analyzer GetAnalyzer()
     {
         return new WhitespaceAnalyzer(); //注意此處如果用SimpleAnalyzer搜索會失敗
     }

  
     private int GetHitCount(String fieldName, String searchString)
     {
         IndexSearcher searcher = new IndexSearcher(dir);

         Term t = new Term(fieldName, searchString);

         Query query = new TermQuery(t);

         Hits hits = searcher.Search(query);

         int hitCount = hits.Length();

         searcher.Close();

         return hitCount;
     }
}
需要注意的是以上所有有關索引的操作,爲了避免頻繁的打開和關閉WriterReader.又由於添加和刪除是不同的連接(Writer, Reader)做的.所以應該儘可能的將添加文檔的操作放在一起批量執行,然後將刪除文檔的操作也放在一起批量執行.避免添加刪除交替進行.

以上所有TestCase的源代碼下載.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章