Lucene.Net(转)
出處:http://www.cnblogs.com/piziyimao/archive/2013/01/31/2887072.html
做過站內(nèi)搜索的朋友應(yīng)該對Lucene.Net不陌生,沒做過的也許會問:就不是個查詢嘛!為什么不能使用Like模糊查找呢?
原因很簡單--模糊查詢的契合度太低,匹配關(guān)鍵字之間不能含有其他內(nèi)容。最重要的是它會造成數(shù)據(jù)庫全表掃描,效率底下,即使使用視圖,也會造成數(shù)據(jù)庫服務(wù)器"亞歷山大",那LuceneNet又是一個神馬東西?如何使用?以下給出詳細(xì)的介紹包括Demo
回到頂部Lucene簡介
首先說明的是--Lucene.Net只是一個全文檢索開發(fā)包,不是一個成型的搜索引擎,
它的功能就是負(fù)責(zé)將文本數(shù)據(jù)按照某種分詞算法進行切詞,分詞后的結(jié)果存儲在索引庫中,從索引庫檢索數(shù)據(jù)的速度灰常快.
對以上加粗的詞匯稍作下闡述:
文本數(shù)據(jù):Lucene.Net只能對文本信息進行檢索,所以非文本信息要么轉(zhuǎn)換成為文本信息,要么你就死了這條心吧!
分詞算法:將一句完整的話分解成若干詞匯的算法? 常見的一元分詞(Lucene.Net內(nèi)置就是一元分詞,效率高,契合度低),二元分詞,基于詞庫的分詞算法(契合度高,效率低)...
切詞:將一句完整的話,按分詞算法切成若干詞語
???? 比如:"不是所有痞子都叫一毛" 這句話,如果根據(jù)一元分詞算法則被切成: 不 是 所 有 痞 子 都 叫 一 毛?
???? 如果二元分詞算法則切成: 不是 是所 所有 有痞 痞子 子都 都叫 叫一? 一毛
???? 如果基于詞庫的算法有可能:不是 所有 痞子 都叫 一毛 具體看詞庫
索引庫:簡單的理解成一個提供了全文檢索功能的數(shù)據(jù)庫
如果文字難以理解 見Demo文件說明中的右側(cè)圖吧
回到頂部效果圖
首先展示效果圖,避免各位觀眾不知偶所云.
這里有三張圖:
圖1 簡單使用頁面效果圖
圖2 對數(shù)據(jù)庫新增數(shù)據(jù)后 索引庫更新效果圖
圖3 將圖2中的新增數(shù)據(jù)修改后 索引庫更新效果圖
回到頂部Demo文件說明
回到頂部簡單使用
圖1中的BookList.aspx 頁面
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="BookList.aspx.cs" Inherits="Web.LuceneNet.BookList" %><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"><title></title> </head> <body><form id="form1" method="get" action="BookList.aspx"><div>請輸入搜索關(guān)鍵字:<input type="text" name="SearchKey" value="" /><input type="submit" name="btnSearch" value="一哈哈" /><input type="submit" name="btnCreate" value="創(chuàng)建索引" /><br /><ul><asp:Repeater ID="Repeater1" runat="server"><ItemTemplate><li><a href='#'><%# Eval("Title") %></a></li><li><span><%# Eval("ContentDescription") %></span></li></ItemTemplate></asp:Repeater></ul></div></form> </body> </html>BookList.aspx.cs 后臺的處理操作
using System; using System.Collections.Generic; using System.IO; using Lucene.Net.Analysis.PanGu; using Lucene.Net.Documents; using Lucene.Net.Index; using Lucene.Net.Search; using Lucene.Net.Store; using PZYM.Shop.BLL;namespace Web.LuceneNet {public partial class BookList : System.Web.UI.Page {protected void Page_Load(object sender, EventArgs e) {string btnCreate = Request.QueryString["btnCreate"];string btnSearch = Request.QueryString["btnSearch"];if(!string.IsNullOrEmpty(btnCreate)) {//創(chuàng)建索引庫CreateIndexByData();}if(!string.IsNullOrEmpty(btnSearch)) {//搜索SearchFromIndexData();}}/// <summary>/// 創(chuàng)建索引/// </summary>private void CreateIndexByData() {string indexPath = Context.Server.MapPath("~/IndexData");//索引文檔保存位置????????? FSDirectory directory = FSDirectory.Open(new DirectoryInfo(indexPath), new NativeFSLockFactory());//IndexReader:對索引庫進行讀取的類bool isExist = IndexReader.IndexExists(directory); //是否存在索引庫文件夾以及索引庫特征文件if(isExist) {//如果索引目錄被鎖定(比如索引過程中程序異常退出或另一進程在操作索引庫),則解鎖//Q:存在問題 如果一個用戶正在對索引庫寫操作 此時是上鎖的 而另一個用戶過來操作時 將鎖解開了 于是產(chǎn)生沖突 --解決方法后續(xù)if(IndexWriter.IsLocked(directory)) {IndexWriter.Unlock(directory);}}//創(chuàng)建向索引庫寫操作對象 IndexWriter(索引目錄,指定使用盤古分詞進行切詞,最大寫入長度限制)//補充:使用IndexWriter打開directory時會自動對索引庫文件上鎖IndexWriter writer = new IndexWriter(directory, new PanGuAnalyzer(), !isExist, IndexWriter.MaxFieldLength.UNLIMITED);BooksManager bookManager = new BooksManager();List<PZYM.Shop.Model.Books> bookList = bookManager.GetModelList("");//--------------------------------遍歷數(shù)據(jù)源 將數(shù)據(jù)轉(zhuǎn)換成為文檔對象 存入索引庫foreach(var book in bookList) {Document document = new Document(); //new一篇文檔對象 --一條記錄對應(yīng)索引庫中的一個文檔//向文檔中添加字段 Add(字段,值,是否保存字段原始值,是否針對該列創(chuàng)建索引)document.Add(new Field("id", book.Id.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED));//--所有字段的值都將以字符串類型保存 因為索引庫只存儲字符串類型數(shù)據(jù)//Field.Store:表示是否保存字段原值。指定Field.Store.YES的字段在檢索時才能用document.Get取出原值? //Field.Index.NOT_ANALYZED:指定不按照分詞后的結(jié)果保存--是否按分詞后結(jié)果保存取決于是否對該列內(nèi)容進行模糊查詢document.Add(new Field("title", book.Title, Field.Store.YES, Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS));//Field.Index.ANALYZED:指定文章內(nèi)容按照分詞后結(jié)果保存 否則無法實現(xiàn)后續(xù)的模糊查詢 //WITH_POSITIONS_OFFSETS:指示不僅保存分割后的詞 還保存詞之間的距離document.Add(new Field("content", book.ContentDescription, Field.Store.YES, Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS));writer.AddDocument(document); //文檔寫入索引庫}writer.Close();//會自動解鎖directory.Close(); //不要忘了Close,否則索引結(jié)果搜不到}/// <summary>/// 從索引庫中檢索關(guān)鍵字/// </summary>private void SearchFromIndexData() {string indexPath = Context.Server.MapPath("~/IndexData");FSDirectory directory = FSDirectory.Open(new DirectoryInfo(indexPath), new NoLockFactory());IndexReader reader = IndexReader.Open(directory, true);IndexSearcher searcher = new IndexSearcher(reader);//搜索條件PhraseQuery query = new PhraseQuery();//把用戶輸入的關(guān)鍵字進行分詞foreach(string word in Common.SplitContent.SplitWords(Request.QueryString["SearchKey"])) {query.Add(new Term("content", word));}//query.Add(new Term("content", "C#"));//多個查詢條件時 為且的關(guān)系query.SetSlop(100); //指定關(guān)鍵詞相隔最大距離//TopScoreDocCollector盛放查詢結(jié)果的容器TopScoreDocCollector collector = TopScoreDocCollector.create(1000, true);searcher.Search(query, null, collector);//根據(jù)query查詢條件進行查詢,查詢結(jié)果放入collector容器//TopDocs 指定0到GetTotalHits() 即所有查詢結(jié)果中的文檔 如果TopDocs(20,10)則意味著獲取第20-30之間文檔內(nèi)容 達到分頁的效果ScoreDoc[] docs = collector.TopDocs(0, collector.GetTotalHits()).scoreDocs;//展示數(shù)據(jù)實體對象集合List<PZYM.Shop.Model.Books> bookResult = new List<PZYM.Shop.Model.Books>();for(int i = 0; i < docs.Length; i++) {int docId = docs[i].doc;//得到查詢結(jié)果文檔的id(Lucene內(nèi)部分配的id)Document doc = searcher.Doc(docId);//根據(jù)文檔id來獲得文檔對象DocumentPZYM.Shop.Model.Books book = new PZYM.Shop.Model.Books();book.Title = doc.Get("title");//book.ContentDescription = doc.Get("content");//未使用高亮//搜索關(guān)鍵字高亮顯示 使用盤古提供高亮插件book.ContentDescription = Common.SplitContent.HightLight(Request.QueryString["SearchKey"], doc.Get("content"));book.Id = Convert.ToInt32(doc.Get("id"));bookResult.Add(book);}Repeater1.DataSource = bookResult;Repeater1.DataBind();}} }使用的分詞方法與關(guān)鍵字變紅 SplitContent.cs
using System.Collections.Generic; using System.IO; using Lucene.Net.Analysis; using Lucene.Net.Analysis.PanGu; using PanGu;namespace Web.Common {public class SplitContent {public static string[] SplitWords(string content) {List<string> strList = new List<string>();Analyzer analyzer = new PanGuAnalyzer();//指定使用盤古 PanGuAnalyzer 分詞算法TokenStream tokenStream = analyzer.TokenStream("", new StringReader(content));Lucene.Net.Analysis.Token token = null;while((token = tokenStream.Next()) != null) { //Next繼續(xù)分詞 直至返回nullstrList.Add(token.TermText()); //得到分詞后結(jié)果}return strList.ToArray();}//需要添加PanGu.HighLight.dll的引用/// <summary>/// 搜索結(jié)果高亮顯示/// </summary>/// <param name="keyword"> 關(guān)鍵字 </param>/// <param name="content"> 搜索結(jié)果 </param>/// <returns> 高亮后結(jié)果 </returns>public static string HightLight(string keyword, string content) {//創(chuàng)建HTMLFormatter,參數(shù)為高亮單詞的前后綴PanGu.HighLight.SimpleHTMLFormatter simpleHTMLFormatter =new PanGu.HighLight.SimpleHTMLFormatter("<font style=\"font-style:normal;color:#cc0000;\"><b>", "</b></font>");//創(chuàng)建 Highlighter ,輸入HTMLFormatter 和 盤古分詞對象SemgentPanGu.HighLight.Highlighter highlighter =new PanGu.HighLight.Highlighter(simpleHTMLFormatter,new Segment());//設(shè)置每個摘要段的字符數(shù)highlighter.FragmentSize = 1000;//獲取最匹配的摘要段return highlighter.GetBestFragment(keyword, content);}} } 回到頂部重點類的說明
Analyzer類:LuceneNet中分詞算法的基類 任何自定義算法都需繼承它
FSDirectory類: 指定索引庫文件存放文件位置? 是Directory的子類(它有兩個子類 還有一個RAMDirecory,它用來指定將索引庫文件存放在內(nèi)存中)
IndexReader:對索引進行讀取的類
???? 靜態(tài)方法bool? IndexExists(Directory directory)--判斷目錄directory是否是一個索引目錄
IndexWriter:對索引進行寫的類
???? 靜態(tài)方法bool? IsLocked(Directory directory)--判斷目錄是否鎖定
???? 它在對索引目錄寫之前會把目錄鎖定,兩個IndexWrite無法同時操作一個索引文件
???? IndexWrite在進行寫操作的時候會自動加鎖
???? Close自動解鎖
???? Unlock手動解鎖(通常用在程序異常退出 IndexWrite還沒來得及close)
Document類:要檢索的文檔 相當(dāng)于一條記錄
???? Add(Field field)向文檔中添加字段
Filed類:構(gòu)造函數(shù)(字段名,字段值,是否存儲原文,是否對該字段創(chuàng)建索引,存儲索引詞間距)
???? 是否存儲原文:Field.Store.YES 存儲原值(如顯示原內(nèi)容必須為YES) Field.Store.NO不存儲原值 Field.Store.YES壓縮存儲
???? 是否創(chuàng)建索引:Field.Index.NOT_ANALYZED不創(chuàng)建索引 Field.Index.ANALYZED創(chuàng)建索引(利于檢索)
IndexSearcher:搜索類?Searcher類的子類
???? Search(查詢條件Query,過濾條件Filter,檢索見過存放容器Collector)
Query類:所有查詢條件父類(子類都具有Add方法)
???? 子類PhraseQuery:多個關(guān)鍵詞的拼接類 關(guān)鍵詞間是且的關(guān)系
???? query.Add(new Term("字段名", 關(guān)鍵詞))
???? query.Add(new Term("字段名2", 關(guān)鍵詞2))?
???? 類似于:where 字段名 contains 關(guān)鍵詞 and 字段名2 contains 關(guān)鍵詞2
???? 子類BooleanQuery:類似PharseQuery 通過它實現(xiàn)關(guān)鍵詞間的或關(guān)系(MUST必須有 Should可有可無? MUST_NOT必須沒有 詳見BookList2.aspx.cs代碼)
回到頂部存在問題
上述只是Lucene.Net的簡單使用
接下來我們深入探討上述使用過程中存在的一些問題以及指的改進的地方:
Q1:創(chuàng)建索引事件耗時的操作,尤其是在數(shù)據(jù)量很大的情況下,索引庫生成耗時是個問題
Q2:真實項目中肯定不可能存在創(chuàng)建索引按鈕,那創(chuàng)建索引的事件什么時候觸發(fā),由誰觸發(fā)呢?
Q3:如代碼中的Q一樣 多個用戶共同操作索引庫時的并發(fā)問題
?
解答上述三個問題
A1.耗時的操作當(dāng)然另起一個后臺線程來完成撒
A2.在網(wǎng)站Application_Start的時,利用A1中的后臺線程循環(huán)監(jiān)聽Books表的增刪改操作,在對Books表的增刪改成功之后,對索引庫相對應(yīng)的數(shù)據(jù)做出增刪改操作
A3.并發(fā)問題最好的解決方式--建立請求隊列,單線程處理隊列,類似于操作系統(tǒng)的中的生產(chǎn)者消費者模式
回到頂部調(diào)整后
IndexManager.cs類中定義后臺線程 循環(huán)監(jiān)聽請求隊列 負(fù)責(zé)對索引庫的更新操作
using System.Collections.Generic; using System.IO; using System.Threading; using System.Web; using Lucene.Net.Analysis.PanGu; using Lucene.Net.Documents; using Lucene.Net.Index; using Lucene.Net.Store; using PZYM.Shop.Model;namespace Web.LuceneNet {public class IndexManager {public static readonly IndexManager bookIndex = new IndexManager();public static readonly string indexPath = HttpContext.Current.Server.MapPath("~/IndexData");private IndexManager() {}//請求隊列 解決索引目錄同時操作的并發(fā)問題private Queue<BookViewMode> bookQueue = new Queue<BookViewMode>();/// <summary>/// 新增Books表信息時 添加邢增索引請求至隊列/// </summary>/// <param name="books"></param>public void Add(Books books) {BookViewMode bvm = new BookViewMode();bvm.Id = books.Id;bvm.Title = books.Title;bvm.IT = IndexType.Insert;bvm.Content = books.ContentDescription;bookQueue.Enqueue(bvm);}/// <summary>/// 刪除Books表信息時 添加刪除索引請求至隊列/// </summary>/// <param name="bid"></param>public void Del(int bid) {BookViewMode bvm = new BookViewMode();bvm.Id = bid;bvm.IT = IndexType.Delete;bookQueue.Enqueue(bvm);}/// <summary>/// 修改Books表信息時 添加修改索引(實質(zhì)上是先刪除原有索引 再新增修改后索引)請求至隊列/// </summary>/// <param name="books"></param>public void Mod(Books books) {BookViewMode bvm = new BookViewMode();bvm.Id = books.Id;bvm.Title = books.Title;bvm.IT = IndexType.Modify;bvm.Content = books.ContentDescription;bookQueue.Enqueue(bvm);}public void StartNewThread() {ThreadPool.QueueUserWorkItem(new WaitCallback(QueueToIndex));}//定義一個線程 將隊列中的數(shù)據(jù)取出來 插入索引庫中private void QueueToIndex(object para) {while(true) {if(bookQueue.Count > 0) {CRUDIndex();} else {Thread.Sleep(3000);}}}/// <summary>/// 更新索引庫操作/// </summary>private void CRUDIndex() {FSDirectory directory = FSDirectory.Open(new DirectoryInfo(indexPath), new NativeFSLockFactory());bool isExist = IndexReader.IndexExists(directory);if(isExist) {if(IndexWriter.IsLocked(directory)) {IndexWriter.Unlock(directory);}}IndexWriter writer = new IndexWriter(directory, new PanGuAnalyzer(), !isExist, IndexWriter.MaxFieldLength.UNLIMITED);while(bookQueue.Count > 0) {Document document = new Document();BookViewMode book = bookQueue.Dequeue();if(book.IT == IndexType.Insert) {document.Add(new Field("id", book.Id.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED));document.Add(new Field("title", book.Title, Field.Store.YES, Field.Index.ANALYZED,Field.TermVector.WITH_POSITIONS_OFFSETS));document.Add(new Field("content", book.Content, Field.Store.YES, Field.Index.ANALYZED,Field.TermVector.WITH_POSITIONS_OFFSETS));writer.AddDocument(document);} else if(book.IT == IndexType.Delete) {writer.DeleteDocuments(new Term("id", book.Id.ToString()));} else if(book.IT == IndexType.Modify) {//先刪除 再新增writer.DeleteDocuments(new Term("id", book.Id.ToString()));document.Add(new Field("id", book.Id.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED));document.Add(new Field("title", book.Title, Field.Store.YES, Field.Index.ANALYZED,Field.TermVector.WITH_POSITIONS_OFFSETS));document.Add(new Field("content", book.Content, Field.Store.YES, Field.Index.ANALYZED,Field.TermVector.WITH_POSITIONS_OFFSETS));writer.AddDocument(document);}}writer.Close();directory.Close();}}public class BookViewMode {public int Id {get;set;}public string Title {get;set;}public string Content {get;set;}public IndexType IT {get;set;}}//操作類型枚舉public enum IndexType {Insert,Modify,Delete} }BookList2.aspx與BookList.aspx大同小異 這里不給出了
BookList2.aspx.cs 對數(shù)據(jù)庫數(shù)據(jù)新增 修改? 刪除等操作
using System; using System.Collections.Generic; using System.IO; using Lucene.Net.Documents; using Lucene.Net.Index; using Lucene.Net.Search; using Lucene.Net.Store; using PZYM.Shop.BLL; using PZYM.Shop.Model;namespace Web.LuceneNet {public partial class BookList2 : System.Web.UI.Page {protected void Page_Load(object sender, EventArgs e) {string btnInsert = Request.QueryString["btnInsert"];string btnSearch = Request.QueryString["btnSearch"];if(!string.IsNullOrEmpty(btnInsert)) {//數(shù)據(jù)新增至索引庫InsertToIndex();}if(!string.IsNullOrEmpty(btnSearch)) {//搜索SearchFromIndexData();}}//臨時數(shù)據(jù)代替表單提交private void InsertToIndex() {//創(chuàng)建一條臨時數(shù)據(jù)Books book = new Books();book.Author = "痞子一毛";book.Title = "piziyimao";book.CategoryId = 1;book.ContentDescription = "不是所有痞子都叫一毛不是所有痞子都叫一毛不是所有痞子都叫一毛不是所有痞子都叫一毛";book.PublisherId = 1;book.ISBN = "124365";book.WordsCount = 1000000;book.UnitPrice = 88;book.CategoryId = 1;book.Clicks = 10;book.PublishDate = DateTime.Now;BooksManager bm = new BooksManager();//IndexManager.bookIndex.Add()數(shù)據(jù)新增 索引庫更新測試//int insertId;//if((insertId = bm.Add(book)) > 0) {// book.Id = insertId;// IndexManager.bookIndex.Add(book);//}//IndexManager.bookIndex.Mod()數(shù)據(jù)修改 索引庫更新測試book.Id = 10001;//數(shù)據(jù)庫生成主鍵IDbook.ContentDescription = "儂好哇, 記住不是所有痞子都叫一毛喲";bm.Update(book);IndexManager.bookIndex.Mod(book);}/// <summary>/// 從索引庫中檢索關(guān)鍵字/// </summary>private void SearchFromIndexData() {string indexPath = Context.Server.MapPath("~/IndexData");FSDirectory directory = FSDirectory.Open(new DirectoryInfo(indexPath), new NoLockFactory());IndexReader reader = IndexReader.Open(directory, true);IndexSearcher searcher = new IndexSearcher(reader);//--------------------------------------這里配置搜索條件//PhraseQuery query = new PhraseQuery();//foreach(string word in Common.SplitContent.SplitWords(Request.QueryString["SearchKey"])) {// query.Add(new Term("content", word));//這里是 and關(guān)系//}//query.SetSlop(100);//關(guān)鍵詞Or關(guān)系設(shè)置BooleanQuery queryOr = new BooleanQuery();TermQuery query = null;foreach(string word in Common.SplitContent.SplitWords(Request.QueryString["SearchKey"])) {query = new TermQuery(new Term("content", word));queryOr.Add(query, BooleanClause.Occur.SHOULD);//這里設(shè)置 條件為Or關(guān)系}//--------------------------------------TopScoreDocCollector collector = TopScoreDocCollector.create(1000, true);//searcher.Search(query, null, collector);searcher.Search(queryOr, null, collector);ScoreDoc[] docs = collector.TopDocs(0, 10).scoreDocs;//取前十條數(shù)據(jù) 可以通過它實現(xiàn)LuceneNet搜索結(jié)果分頁List<PZYM.Shop.Model.Books> bookResult = new List<PZYM.Shop.Model.Books>();for(int i = 0; i < docs.Length; i++) {int docId = docs[i].doc;Document doc = searcher.Doc(docId);PZYM.Shop.Model.Books book = new PZYM.Shop.Model.Books();book.Title = doc.Get("title");book.ContentDescription = Common.SplitContent.HightLight(Request.QueryString["SearchKey"], doc.Get("content"));book.Id = Convert.ToInt32(doc.Get("id"));bookResult.Add(book);}Repeater1.DataSource = bookResult;Repeater1.DataBind();}} }Global.ascx中設(shè)置
protected void Application_Start(object sender, EventArgs e) {IndexManager.bookIndex.StartNewThread();} 回到頂部Lucene.Net博文與資源下載
http://www.cnblogs.com/birdshover/category/152283.html
http://www.360doc.com/content/13/0509/08/5054188_284048627.shtml
點擊此處下載示例代碼
總結(jié)
以上是生活随笔為你收集整理的Lucene.Net(转)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 电动剃须刀和手动剃须刀哪个干净(电动汽车
- 下一篇: ap隔离是什么意思