Lucene.net 系列二 --- index (上) - -
--水木尤寒
Lucene建立Index的過程:
1. 抽取文本.
比如將PDF以及Word中的內(nèi)容以純文本的形式提取出來.Lucene所支持的類型主要為String,為了方便同時(shí)也支持Date 以及Reader.其實(shí)如果使用這兩個(gè)類型lucene會自動進(jìn)行類型轉(zhuǎn)換.
2. 文本分析.
Lucene將針對所給的文本進(jìn)行一些最基本的分析,并從中去除一些不必要的信息,比如一些常用字a ,an, the 等等,如果搜索的時(shí)候不在乎字母的大小寫, 又可以去掉一些不必要的信息.總而言之你可以把這個(gè)過程想象成一個(gè)文本的過濾器,所有的文本內(nèi)容通過分析, 將過濾掉一些內(nèi)容,剩下最有用的信息.
3. 寫入index.
和google等常用的索引技術(shù)一樣lucene在寫index的時(shí)候都是采用的倒排索引技術(shù)(inverted index.) 簡而言之,就是通過某種方法(類似hash表?)將常見的”一篇文檔中含有哪些詞”這個(gè)問題轉(zhuǎn)成”哪篇文檔中有這些詞”. 而各個(gè)搜索引擎的索引機(jī)制的不同主要在于如何為這張倒排表添加更準(zhǔn)確的描述.比如google有名的PageRank因素.Lucene當(dāng)然也有自己的技術(shù),希望在以后的文章中能為大家加以介紹.
在上一篇文章中,使用了最基本的建立索引的方法.在這里將對某些問題加以詳細(xì)的討論.
1. 添加Document至索引
上次添加的每份文檔的信息是一樣的,都是文檔的filename和contents.
還有一點(diǎn)Lucene支持對Field進(jìn)行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])); 這點(diǎn)純粹是為了方便用戶的使用.在內(nèi)部Lucene自動做了轉(zhuǎn)化,效果和將它們拼接好再存是一樣.
2. 刪除索引中的文檔
這一點(diǎn)Lucene所采取的方式比較怪,它使用IndexReader來對要刪除的項(xiàng)進(jìn)行標(biāo)記,然后在Reader Close的時(shí)候一起刪除.
這里簡要介紹幾個(gè)方法.
IndexReader reader = IndexReader.Open(dir);
reader.Delete(new Term("city", "Amsterdam"));
reader.Close(); 你還可以通過reader.UndeleteAll()這個(gè)方法取消前面所做的標(biāo)記,即在read.Close()調(diào)用之前取消所有的刪除工作.
3. 更新索引中的文檔
這個(gè)功能Lucene沒有支持, 只有通過刪除后在添加來實(shí)現(xiàn). 看看代碼,很好理解的.
需要注意的是以上所有有關(guān)索引的操作,為了避免頻繁的打開和關(guān)閉Writer和Reader.又由于添加和刪除是不同的連接(Writer, Reader)做的.所以應(yīng)該盡可能的將添加文檔的操作放在一起批量執(zhí)行,然后將刪除文檔的操作也放在一起批量執(zhí)行.避免添加刪除交替進(jìn)行.
以上所有TestCase的源代碼下載.
[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;
}
}
[TestFixture]
public class DocumentDeleteTest : BaseIndexingTestCase // BaseIndexingTestCase中的SetUp方法 //建立了索引其中加入了兩個(gè)Document
{
[Test]
public void testDeleteBeforeIndexMerge()
{
IndexReader reader = IndexReader.Open(dir); //當(dāng)前索引中有兩個(gè)Document
Assert.AreEqual(2, reader.MaxDoc()); //文檔從0開始計(jì)數(shù),MaxDoc表示下一個(gè)文檔的序號
Assert.AreEqual(2, reader.NumDocs()); //NumDocs表示當(dāng)前索引中文檔的個(gè)數(shù)
reader.Delete(1); //對標(biāo)號為1的文檔標(biāo)記為待刪除,邏輯刪除
Assert.IsTrue(reader.IsDeleted(1)); //檢測某個(gè)序號的文檔是否被標(biāo)記刪除
Assert.IsTrue(reader.HasDeletions()); //檢測索引中是否有Document被標(biāo)記刪除
Assert.AreEqual(2, reader.MaxDoc()); //當(dāng)前下一個(gè)文檔序號仍然為2
Assert.AreEqual(1, reader.NumDocs()); //當(dāng)前索引中文檔數(shù)變成1
reader.Close(); //此時(shí)真正從物理上刪除之前被標(biāo)記的文檔
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()); //索引重排后,下一個(gè)文檔序號變?yōu)?
Assert.AreEqual(1, reader.NumDocs());
reader.Close();
}
} 當(dāng)然你也可以不通過文檔序號進(jìn)行刪除工作.采用下面的方法,可以從索引中刪除包含特定的內(nèi)容文檔.
doc.Add(Field.Keyword("filename", file.FullName));
doc.Add(Field.Text("contents", new StreamReader(file.FullName)));
在Lucene中對每個(gè)文檔的描述是可以不同的,比如,兩份文檔都是描述一個(gè)人,其中一個(gè)添加的是name, age 另一個(gè)添加的是id, sex ,這種不規(guī)則的文檔描述在Lucene中是允許的.