1. 程式人生 > >Lucene.Net+盤古分詞 實現搜尋 Quartz.Net(定時任務) 實現熱詞統計

Lucene.Net+盤古分詞 實現搜尋 Quartz.Net(定時任務) 實現熱詞統計

一 :Lucene.Net  +盤古分詞 實現搜尋功能

   1 、Lucene.Net的配置:

        引入相關dll檔案 ( Lucene.Net、log4net、PanGu、PanGu.HighLight、PanGu.Lucene.Analyzer)。當用戶新增一條記錄的時候 ,先向資料庫寫入資料,然後馬上寫入Lucene.Net (為了解決多執行緒併發的問題,需要引入佇列,並且使用單例模式使佇列是唯一的)

        示例程式碼:

 public sealed class IndexManager
    {
        private static readonly IndexManager indexManager = new IndexManager();  //具體單例模式原理見《單例模式》一文
        private IndexManager()
        {
        }
        public Queue<SearchBook> queque = new Queue<SearchBook>();
        public static IndexManager GetInstance()
        {
            return indexManager;
        }
        public void ThreadStart()
        {
            Thread thread = new Thread(CommondQueue);
            thread.IsBackground = true;
            thread.Start();
        }
        private void CommondQueue()
        {
            while (true)
            {
                if (queque.Count > 0)
                {
                    WriteToLucene();
                }
                else
                {
                    Thread.Sleep(3000);
                }
            }
        }
        //刪除記錄
        public void Delete(int id)
        {
            SearchBook searchBook = new SearchBook() { ID = id, SearchBookType = Model.EnumType.SearchBookTypeEnum.Delete };
            queque.Enqueue(searchBook);
        }
        //新增記錄
        public void Add(int id, string title, string content)
        {
            SearchBook searchBook = new SearchBook() { ID = id, Title = title, Content = content, SearchBookType = Model.EnumType.SearchBookTypeEnum.Add };
            queque.Enqueue(searchBook);

        }
        //從佇列裡取資料寫入Lucene
        private void WriteToLucene()
        {
            string indexPath = @"C:\Users\Administrator\Desktop\Project.OA12晚\資料\lucenedir";//注意和磁碟上資料夾的大小寫一致,否則會報錯。將建立的分詞內容放在該目錄下。
            FSDirectory directory = FSDirectory.Open(new DirectoryInfo(indexPath), new NativeFSLockFactory());//指定索引檔案(開啟索引目錄) FS指的是就是FileSystem
            bool isUpdate = IndexReader.IndexExists(directory);//IndexReader:對索引進行讀取的類。該語句的作用:判斷索引庫資料夾是否存在以及索引特徵檔案是否存在。
            if (isUpdate)
            {
                //同時只能有一段程式碼對索引庫進行寫操作。當使用IndexWriter開啟directory時會自動對索引庫檔案上鎖。
                //如果索引目錄被鎖定(比如索引過程中程式異常退出),則首先解鎖(提示一下:如果我現在正在寫著已經加鎖了,但是還沒有寫完,這時候又來一個請求,那麼不就解鎖了嗎?這個問題後面會解決)
                if (IndexWriter.IsLocked(directory))
                {
                    IndexWriter.Unlock(directory);
                }
            }
            IndexWriter writer = new IndexWriter(directory, new PanGuAnalyzer(), !isUpdate, Lucene.Net.Index.IndexWriter.MaxFieldLength.UNLIMITED);//向索引庫中寫索引。這時在這裡加鎖。

            while (queque.Count > 0)
            {
                SearchBook searchBook = queque.Dequeue();//取出資料 
                writer.DeleteDocuments(new Term("id", searchBook.ID.ToString()));//先把它刪了
                if (searchBook.SearchBookType == Model.EnumType.SearchBookTypeEnum.Delete)//如果是刪除
                {
                    continue;//跳出本次迴圈
                }//否則新增(先刪除再新增  相當於修改)
                string txt = searchBook.Content;
                Document document = new Document();//表示一篇文件。
                                                   //Field.Store.YES:表示是否儲存原值。只有當Field.Store.YES在後面才能用doc.Get("number")取出值來.Field.Index. NOT_ANALYZED:不進行分詞儲存
                document.Add(new Field("id", searchBook.ID.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED));
                document.Add(new Field("title", searchBook.Title, Field.Store.YES, Field.Index.NOT_ANALYZED));
                //Field.Index. ANALYZED:進行分詞儲存:也就是要進行全文的欄位要設定分詞 儲存(因為要進行模糊查詢)
                //Lucene.Net.Documents.Field.TermVector.WITH_POSITIONS_OFFSETS:不僅儲存分詞還儲存分詞的距離。
                document.Add(new Field("content", txt, Field.Store.YES, Field.Index.ANALYZED, Lucene.Net.Documents.Field.TermVector.WITH_POSITIONS_OFFSETS));
                writer.AddDocument(document);
            }
            writer.Close();//會自動解鎖。
            directory.Close();//不要忘了Close,否則索引結果搜不到
        }
    }
   搜尋部分 注意請求要以GET形式傳送(作用是把網址複製下來發給其他人其他人也可以看到搜尋結果)

  示例程式碼:  

 private void SearchFromContent()
        {
            string indexPath = Request.MapPath(@"\資料\lucenedir");
            //string indexPath = @"C:\Users\Administrator\Desktop\Project.OA12晚\資料\lucenedir";
            List<string> kws = Common.PanGuLuceneHelper.FenCi(Request["txtsearch"]);//對使用者輸入的搜尋條件進行拆分。  幫助類見下文
            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 kws)//先用空格,讓使用者去分詞,空格分隔的就是詞“計算機   專業”
            {
                query.Add(new Term("content", word));
            }
            //query.Add(new Term("body","語言"));--可以新增查詢條件,兩者是add關係.順序沒有關係.
            // query.Add(new Term("body", "大學生"));
            // query.Add(new Term("body", kw));//body中含有kw的文章
            query.SetSlop(100);//多個查詢條件的詞之間的最大距離.在文章中相隔太遠 也就無意義.(例如 “大學生”這個查詢條件和"簡歷"這個查詢條件之間如果間隔的詞太多也就沒有意義了。)
            //TopScoreDocCollector是盛放查詢結果的容器
            TopScoreDocCollector collector = TopScoreDocCollector.create(1000, true);
            searcher.Search(query, null, collector);//根據query查詢條件進行查詢,查詢結果放入collector容器
            ScoreDoc[] docs = collector.TopDocs(0, collector.GetTotalHits()).scoreDocs;//得到所有查詢結果中的文件,GetTotalHits():表示總條數   TopDocs(300, 20);//表示得到300(從300開始),到320(結束)的文件內容.
            //可以用來實現分頁功能
            List<SearchBook> list = new List<SearchBook>();
            for (int i = 0; i < docs.Length; i++)
            {
                //
                //搜尋ScoreDoc[]只能獲得文件的id,這樣不會把查詢結果的Document一次性載入到記憶體中。降低了記憶體壓力,需要獲得文件的詳細內容的時候通過searcher.Doc來根據文件id來獲得文件的詳細內容物件Document.
                int docId = docs[i].doc;//得到查詢結果文件的id(Lucene內部分配的id)
                Document doc = searcher.Doc(docId);//找到文件id對應的文件詳細資訊
                SearchBook searchBook = new SearchBook() { ID = int.Parse(doc.Get("id")), Content =Common.PanGuLuceneHelper.PanGuHighLight(Request["txtsearch"],doc.Get("content")) , Title = doc.Get("title") };
                list.Add(searchBook);
            }
            ViewData["list"] = list;
        }

盤古分詞幫助類(把使用者搜尋的詞進行拆分,並在搜尋結果中把搜尋詞高亮顯示):(注意在前臺輸出的時候要寫@MvcHtmlString.Create()輸出用來顯示出高亮樣式)
 public class PanGuLuceneHelper
    {
        public static List<string> FenCi(string str)
        {
            Analyzer analyzer = new PanGuAnalyzer();
            TokenStream tokenStream = analyzer.TokenStream("", new StringReader(str));
            Lucene.Net.Analysis.Token token = null;
            List<string> list = new List<string>();
            while ((token = tokenStream.Next()) != null)
            {
                list.Add(token.TermText());
            }
            return list;
        }

        public static string PanGuHighLight(string keyWords,string content)
        {

            PanGu.HighLight.SimpleHTMLFormatter simpleHTMLFormatter =
             new PanGu.HighLight.SimpleHTMLFormatter("<font color=\"red\">", "</font>");
            //建立Highlighter ,輸入HTMLFormatter 和盤古分詞物件Semgent
            PanGu.HighLight.Highlighter highlighter =
            new PanGu.HighLight.Highlighter(simpleHTMLFormatter,
            new Segment());
            //設定每個摘要段的字元數
            highlighter.FragmentSize = 150;
            //獲取最匹配的摘要段
            return highlighter.GetBestFragment(keyWords, content);
        }