通常的網站都會有都會有搜索的功能,通常實現搜索主要有三種方案算法
第一種是最差的,也是最不推薦的,使用數據庫的模糊查詢例如select * form table where 字段 like XXX,這種查詢的缺點很明顯:數據庫
(1) 沒法查找幾個關鍵詞不連在一塊兒的狀況測試
(2) 全表掃描 效率低下網站
第二種:使用SqlServer的全文本檢索功能this
舉例:select * form table where msg = ‘江蘇南京’搜索引擎
這是就能夠寫成select * form table where msg.contains(‘江蘇南京’);編碼
這樣搜索出來的結果就能夠既包含江蘇也能夠包含南京,而且匹配的速度也快,還能夠實現分詞。spa
缺點:.net
(1):只有正版的SqlServer才支持上面的技術code
(2):數據庫的分詞不太靈活,不能本身修改詞庫
第三種:使用lucene.Net(本文重點講解)
Lucene.Net只是一個全文檢索開發包,不是一個成熟的搜索引擎,他的功能就是:將數據交給Lucene.Net, 查詢數據的時候從Lucene.Net查詢數據,能夠當作是一個提供了全文檢索功能的數據庫,lucene.net只對文本信息進行檢索,若是不是文本信息,要轉換爲文本信息。lucene會將扔給他的詞切詞保存,由於是保存的時候分詞(切詞),因此搜索速度很是快。
分詞是搜索結果好壞的關鍵:
lucene不一樣的分詞算法就是不一樣的類,全部的分詞算法類都從Analyzer類繼承,不一樣的分詞算法有不一樣的優缺點。
例如:內置的StandardAnalyzer是將英文按照空格,標點符號等進行分詞,講中文按照單個字進行分詞,一個漢字算一個詞(就是所謂的一元分詞)
二元分詞:每兩個漢字算一個單詞,「歡迎大家你們」會分爲「歡迎」,迎你,大家,們大,你們 要在網上下載一個二元分詞的算法:CJKAnalyzer
基於詞庫的分詞算法:基於一個詞庫進行分詞,能夠提升分詞的成功率,有庖丁解牛,盤古分詞等。效率低(相對於一元分詞與二元分詞)但準確度較高
注意:lucene.Net對漢語的分詞效果很差,須要藉助於第三方的分詞算法:開源的盤古分詞(能夠在開源中國社區下載,裏面有詳細的Demo,以及dll文件)
編寫代碼以下(體驗盤古分詞):
在中國開源社區下載Pangu分詞forLucene
第一步:將WebDemo裏的bin文件夾下的Dictionaries文件夾複製到項目的根目錄下,而後改文件夾名爲Dict並設置裏面的內容的屬性的若是較新則複製到輸出目錄
第二部:添加引用Lucene.net.dll文件和PanGu.Lucene.Analyzer.dll文件
Analyzer analyzer = new Lucene.Net.Analysis.PanGu.PanGuAnalyzer();
TokenStream tokenStream = analyzer.TokenStream("", new System.IO.StringReader("北京,Hi歡迎大家你們"));
Lucene.Net.Analysis.Token token = null;
while ((token = tokenStream.Next()) != null)
{
ListBox1.Items.Add(token.TermText());
}
既然是分詞,那就確定有詞庫,有詞庫就能夠修改,使用剛纔的Bin文件夾下的PanGu.Lucene.ImportTool.exe文件打開詞庫修改詞庫的內容,就能夠實現最新的分詞效果。
能夠理解爲:
先創建一個索引系統,而後打開索引系統,使用lucene的IndexWriter類向裏面寫入索引(document對象),該對象從盤古分詞對已有的文章的分詞獲得
基本思想如上圖
lucene+PanGu
創建lucene詞庫:(將數據交給lucene,使用Pangu分詞),並實現搜索的功能(第二段代碼)
1 protected void Button4_Click(object sender, EventArgs e) 2 { 3 string indexPath = Server.MapPath(@"/Demo/lucenedir");//注意和磁盤上文件夾的大小寫一致,不然會報錯。將建立的分詞內容放在該目錄下。 4 5 //指定索引文件(打開索引目錄) FS指的是就是FileSystem 個人理解:索引系統 6 FSDirectory directory = FSDirectory.Open(new DirectoryInfo(indexPath), new NativeFSLockFactory()); 7 8 //IndexReader:對索引進行讀取的類。該語句的做用:判斷索引庫文件夾是否存在以及索引特徵文件是否存在。 9 bool isUpdate = IndexReader.IndexExists(directory); 10 if (isUpdate) 11 { 12 //同時只能有一段代碼對索引庫進行寫操做。當使用IndexWriter打開directory時會自動對索引庫文件上鎖。 13 //若是索引目錄被鎖定(好比索引過程當中程序異常退出),則首先解鎖 14 //(提示一下:若是我如今正在寫着已經加鎖了,可是尚未寫完,這時候又來一個請求,那麼不就解鎖了嗎?這個問題後面會解決) 15 if (IndexWriter.IsLocked(directory)) 16 { 17 IndexWriter.Unlock(directory); 18 } 19 } 20 21 //向索引庫中寫索引。這時在這裏加鎖。 22 IndexWriter writer = new IndexWriter(directory, new PanGuAnalyzer(), !isUpdate, Lucene.Net.Index.IndexWriter.MaxFieldLength.UNLIMITED); 23 for (int i = 1; i <= 10; i++) 24 { 25 string txt = File.ReadAllText(Server.MapPath(@"/Demo/測試文件/" + i + ".txt"), System.Text.Encoding.Default);//注意這個地方的編碼 26 Document document = new Document();//表示一篇文檔。 27 //Field.Store.YES:表示是否存儲原值。只有當Field.Store.YES在後面才能用doc.Get("number")取出值來.Field.Index. NOT_ANALYZED:不進行分詞保存 28 document.Add(new Field("number", i.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED)); 29 30 //Field.Index. ANALYZED:進行分詞保存:也就是要進行全文的字段要設置分詞 保存(由於要進行模糊查詢) 31 32 //Lucene.Net.Documents.Field.TermVector.WITH_POSITIONS_OFFSETS:不只保存分詞還保存分詞的距離。 33 document.Add(new Field("body", txt, Field.Store.YES, Field.Index.ANALYZED, Lucene.Net.Documents.Field.TermVector.WITH_POSITIONS_OFFSETS)); 34 writer.AddDocument(document); 35 36 } 37 writer.Close();//會自動解鎖。 38 directory.Close();//不要忘了Close,不然索引結果搜不到 39 }
1 protected void Button5_Click(object sender, EventArgs e) 2 { 3 //建立的分詞的內容所存放的目錄 4 string indexPath = Server.MapPath(@"/Demo/lucenedir"); ; 5 string kw = "C#"; 6 kw = kw.ToLower(); 7 8 FSDirectory directory = FSDirectory.Open(new DirectoryInfo(indexPath), new NoLockFactory()); 9 IndexReader reader = IndexReader.Open(directory, true); 10 IndexSearcher searcher = new IndexSearcher(reader); 11 12 //搜索條件 13 PhraseQuery query = new PhraseQuery(); 14 //foreach (string word in kw.Split(' '))//先用空格,讓用戶去分詞,空格分隔的就是詞「計算機 專業」 15 //{ 16 // query.Add(new Term("body", word)); 17 //} 18 //query.Add(new Term("body","語言"));--能夠添加查詢條件,二者是add關係.順序沒有關係. 19 // query.Add(new Term("body", "大學生")); 20 query.Add(new Term("body", kw));//body中含有kw的文章 21 query.SetSlop(100);//多個查詢條件的詞之間的最大距離.在文章中相隔太遠 也就無心義.(例如 「大學生」這個查詢條件和"簡歷"這個查詢條件之間若是間隔的詞太多也就沒有意義了。) 22 //TopScoreDocCollector是盛放查詢結果的容器 23 TopScoreDocCollector collector = TopScoreDocCollector.create(1000, true); 24 searcher.Search(query, null, collector);//根據query查詢條件進行查詢,查詢結果放入collector容器 25 26 //獲得全部查詢結果中的文檔,GetTotalHits():表示總條數 TopDocs(300, 20);//表示獲得300(從300開始),到320(結束)的文檔內容. 27 //能夠用來實現分頁功能 28 ScoreDoc[] docs = collector.TopDocs(0, collector.GetTotalHits()).scoreDocs; 29 this.ListBox1.Items.Clear(); 30 for (int i = 0; i < docs.Length; i++) 31 { 32 //搜索ScoreDoc[]只能得到文檔的id,這樣不會把查詢結果的Document一次性加載到內存中。 33 //下降了內存壓力,須要得到文檔的詳細內容的時候經過searcher.Doc來根據文檔id來得到文檔的詳細內容對象Document. 34 //獲得查詢結果文檔的id(Lucene內部分配的id) 35 int docId = docs[i].doc; 36 37 //找到文檔id對應的文檔詳細信息 38 Document doc = searcher.Doc(docId); 39 40 // 取出放進字段的值 41 this.ListBox1.Items.Add(doc.Get("number") + "\n"); 42 this.ListBox1.Items.Add(doc.Get("body") + "\n"); 43 this.ListBox1.Items.Add("-----------------------\n"); 44 } 45 }
注意事項:搜索的時候是區分大小寫的,要想不區分,能夠在創建詞庫的時候直接將全部的詞都轉換爲大寫或小寫的,搜索的時候作出相應的轉換便可