天天看點

Lucene&Solr架構之第一篇

版權聲明:本文為部落客原創文章,遵循 CC 4.0 BY-SA 版權協定,轉載請附上原文出處連結和本聲明。

本文連結:https://blog.csdn.net/zhao1299002788/article/details/102651316

2.資訊檢索
		資訊檢索是計算機世界中非常重要的一種功能。資訊檢索不僅僅是指從資料庫檢索資料,還包括從檔案、網頁、郵件、使用者手輸入的内容中檢索資料。通過怎樣的高效方式将使用者想要的資訊快速提取出來,是計算機技術人員研究的重點方向之一。
		2.1.資料分類
		我們生活中的資料總體分為兩種:結構化資料和非結構化資料。
		結構化資料:指具有固定格式或有限長度的資料,如資料庫,中繼資料等。
		非結構化資料:指不定長或無固定格式的資料,如郵件,word文檔等磁盤上的檔案
		2.2.結構化資料搜尋方法
		資料庫就是最常見的結構化資料。通過SQL可以非常友善的查詢資料。
		為什麼資料庫中的資料能非常友善的搜尋出來?
		因為資料庫中的資料存儲在表中,表有行有列有類型有長度,是以才可以通過非常友善的SQL查詢結果。也就是說結構化的資料有規律,是以才好進行查找。
			試想一下如果資料沒有進行結構化,沒有任何規律該如何查詢?
		2.3.非結構化資料查詢方法
		我們考慮一個小時候學查字典的場景:小時候我們都使用過新華字典,老師叫你翻開第268頁從268頁到269頁,找到“坑爹”的坑,此時你會怎麼查找?——毫無疑問,你的眼睛會從268頁的第一個字開始從頭至尾地掃描,直到找到“坑”字為止。這種按照内容的順序一個一個字元的查找方法叫做順序掃描法(Serial Scanning)。對于少量的資料,使用順序掃描是夠用的。

		 

		但是如果老師不告訴你你坑爹的“坑”字在哪一頁呢?也沒有教你如何查字典呢?
		你隻能從第一頁的第一個字逐個的掃描下去,那樣你真的是被坑了。查找的過程會相當的慢,甚至會讓你崩潰,是以這種坑爹的事情我們不能去做。我們要重新思考此時的查詢辦法。

		思考一下新華字典是怎麼解決漢字的快速查找的?
		從一堆沒有結構的内容中提取出來文字的位置資訊(頁碼)、文字寫法(文字本身)、漢語拼音,然後将它們重新整理、排序、歸納,最終形成一張結構化的表,我們叫做漢語拼音音節索引表。
		漢語拼音索引中記錄了“坑”字在哪一頁的資訊,隻要你知道“坑”字的漢語拼音,就可以快速的查找到“坑”字在哪,這樣答案就出來了。
		下圖是漢語拼音音節索引表:
			 

		從新華字典的例子總結一下,如何從一堆沒有規律沒有結構的資訊中快速的查找我們需要的資訊?
		最有效的方法就是先将資訊重新組織(提取、整理、排序、歸納),形成新的集合(即一個更友善更高效查找的集合),然後查詢這個結構化的集合,從中找出你要找的資訊在原文中的位置。
		簡單歸納成:
		非結構化結構化儲存結構化
		查找結構化得到在非結構化中的定位

		這部分從非結構化資料中提取出來,重新組織的結構化資訊,我們稱之索引。
		這種先對全文建立索引集合,再對索引集合進行檢索的查詢方式就叫全文檢索(Full-text Search)。
		建立索引的過程會不會很繁瑣費時?
		是的,很繁瑣費時間,但是值得的,因為索引一旦建立就可以多次使用,最終可以帶來高效的查詢速度,是一件一勞永逸的事情。
		2.4.如何實作全文檢索
		Apache提供了一個開源的全文檢索開發架構——Lucene。它提供了完整的查詢子產品和索引子產品,利用這些核心子產品,開發人員可以友善、快速的開發出全文檢索應用。
		根據上面的簡單歸納可以知道我們需要使用Lucene要做兩件事情:
			非結構化結構化儲存結構化————建立索引
			查找結構化得到在非結構化中是定位————查詢索引

		注意:索引的資料來源不僅僅局限于資料庫,一切可以采集的資料都可以被建立索引。
		2.5.系統的資料查詢方案

		·基本的資料查詢方案在面對查詢量大的應用時會對資料庫造成極大的壓力,而且查詢效率會很低。
		·改進後的資料查詢方案将讀寫進行了分離,将查詢量大的應用的查詢請求分發給了索引庫,查詢直接走索引庫,不走資料庫,這樣就有效的降低了資料庫的壓力,同時索引庫查詢的高效特性也能夠保證查詢效率。
		2.6.全文檢索的應用場景
		全文檢索應用最多的就是開發站内搜尋服務。尤其是對于電商系統,大資料量的搜尋都是使用的站内搜尋服務。
		還有專業的搜尋引擎中也有全文檢索技術的使用,比如百度、Google等,但專業的搜尋引擎不隻使用這一種搜尋技術。
		3.Lucene實作全文檢索的流程
		3.1.建立索引和查詢索引流程

		說明:
		1.綠色表示建立索引過程,包括:
		采集資料建構文檔對象分析文檔對象建立索引(儲存到索引庫)

		2.紅色表示查詢索引過程,包括:
		入口送出查詢請求(查詢關鍵字)建立查詢對象執行查詢(從索引庫搜尋)渲染結果顯示查詢結果
		3.2.索引流程
		使用者将想要搜尋的原始資料建立索引,索引内容存儲在索引庫(index)中。建立索引時不會改變原始文檔的任何内容,隻是将有用資訊的拷貝重新組織成索引。
		假設有如下兩個原始文檔:
				
		【students.txt】:Students should be allowed to go out with their friends, but not allowed to drink beer.
		【myfriends.txt】:My friend Jerry went to school to see his students but found them drunk which is not allowed.

		3.2.1.采集資料

		(手動程式設計)
		從網際網路上、資料庫、檔案系統中等擷取需要搜尋的原始資訊,這個過程就是資訊采集,資訊采集的目的是為了對原始内容進行索引。 
		如何采集資料?
		1、網際網路上的網頁:可以使用工具将網頁抓取到本地生成html檔案。 
		2、資料庫中的資料:可以直接連接配接資料庫用SQL查詢資料。
		3、檔案系統中的檔案:可以通過I/O操作讀取檔案的内容。

		在Internet上采集資訊的軟體通常稱為爬蟲或蜘蛛,也稱為網絡機器人,爬蟲通路網際網路上的每一個網頁,将擷取到的網頁内容存儲起來。
			Lucene不提供資訊采集的類庫,需要自己編寫一個爬蟲程式實作資訊采集,也可以通過一些開源軟體實作資訊采集,如下:
			Nutch(http://lucene.apache.org/nutch), Nutch是apache的一個子項目,包括大規模爬蟲工具,能夠抓取和分辨web網站資料。
			jsoup(http://jsoup.org/ ),jsoup 是一款Java 的HTML解析器,可直接解析某個URL位址、HTML文本内容。它提供了一套非常省力的API,可通過DOM,CSS以及類似于jQuery的操作方法來取出和操作資料。
			heritrix(http://sourceforge.net/projects/archive-crawler/files/),Heritrix 是一個由 java 開發的、開源的網絡爬蟲,使用者可以使用它來從網上抓取想要的資源。其最出色之處在于它良好的可擴充性,友善使用者實作自己的抓取邏輯。
		3.2.2.建構文檔對象
		(手動new)
		采集上來的資料格式各式各樣,我們需要先統一格式然後才能處理,Lucene中的統一格式就是Document文檔對象。
		一個Document對象類似資料庫表的一條記錄,可以包含多個Field域,Field域有兩個屬性:域名(name)和域值(value),Field就相當于表的字段。
		Document結構更加靈活,不限制Field域的數量、種類和是否重複,隻要是Field就可以加入Document對象。
		和資料庫表類似,每個文檔都有一個唯一的主鍵——文檔id(docID)。


		  
		上圖是将磁盤上的一個檔案采集出來的資料放入一個Document對象。Document對象中包括四個Field(file_name、file_path、file_size、file_content)
		3.2.3.分析文檔對象(重點)
		(Lucene自動完成)
		分析文檔主要是對文檔的Field域進行分析,目的是為了建立索引做好準備。分析的過程是将域(Field)的内容轉換成最基本的索引單元——項(Term)的過程。

		3.2.3.1.分詞元件(Tokenizer)
		分詞元件(Tokenizer)會做以下幾件事情(這個過程稱為:Tokenize):
		1. 分詞器将Field域内容分成一個一個單獨的單詞
		2. 标點符号過濾器去除内容中的标點符号
		3. 停用詞過濾器去除停用詞(stop word)
		什麼是停用詞?所謂停詞(Stop word)就是一種語言中沒有具體含義的詞,因而大多數情況下不會作為搜尋的關鍵詞,這樣一來建立索引時能減少索引的大小。英語中停詞(Stop word)如:”the”、”a”、”this”,中文有:”的,得”等。不同語種的分詞元件(Tokenizer),都有自己的停詞(stop word)集合。

		經過分詞(Tokenize)之後得到的結果稱為詞彙單元(Token)。上面的兩個文檔的content域内容經過分析後得到以下詞彙單元:
		“Students”,“allowed”,“go”,“their”,“friends”,“allowed”,“drink”,“beer”,“My”,“friend”,“Jerry”,“went”,“school”,“see”,“his”,“students”,“found”,“them”,“drunk”,“allowed”
		将Token傳給語言處理元件。
		3.2.3.2.語言處理元件(Linguistic Processor)
		語言處理元件(linguistic processor)主要是對得到的詞元(Token)做一些語言相關的處理。對于英語,語言處理元件(Linguistic Processor)一般做以下幾點:
		1. 變為小寫(Lowercase)
		2. 複數變單數(stemming) 如”cars”到”car”
		3. 詞形還原(lemmatization) ,如”drove”到”drive”
		經過語言處理元件(linguistic processor)處理之後得到的結果稱為詞項(Term),它是建立索引的最小單元。上面的Token經過處理後得到的詞項(Term)如下:
		"student","allow","go","their","friend","allow","drink","beer","my","friend","jerry","go","school","see","his","student","find","them","drink","allow"。
		經過語言處理後,搜尋drive時原文中是drove的也能被搜尋出來。對文檔中的各個Field域進行逐個分析,最終形成了許多的Term詞項。

		綜上所述,分析文檔的最終産物是Term,Term是建立索引的最小單元,也是搜尋索引時的最小單元。
		3.2.4.建立索引
		(Lucene自動完成)

		3.2.4.1.建立字典表
		利用得到的詞項(Term)建立一個字典表,一列是Term詞項,一列是文檔ID(DocId)
			字典表如下:
		Term	DocId
		student	1
		allow	1
		go	1
		their	1
		friend	1
		allow	1
		drink	1
		beer	1
		my	2
		friend	2
		jerry	2
		go	2
		school	2
		see	2
		his	2
		student	2
		find	2
		them	2
		drink	2
		allow	2

		3.2.4.2.對字典表按字母順序排序
		對字典表按字母順序排序:
			排序結果如下:
		Term	DocId
		allow	1
		allow	1
		allow	2
		beer	1
		drink	1
		drink	2
		find	2
		friend	1
		friend	2
		go	1
		go	2
		his	2
		jerry	2
		my	2
		school	2
		see	2
		student	1
		student	2
		their	1
		them	2

		3.2.4.3.合并相同詞項,歸納文檔倒排連結清單
		建立好的Term詞項實際是包含兩部分資訊:一是Term出自哪個域,二是Term的内容。合并相同的詞項(Term)成為文檔倒排(Posting List)連結清單。
		●合并規則:
		●在比較Term是否相同時,不考慮是否在同一個Document對象中,合并時暫時忽略它。
		●不同的域(Field)中拆分出來的相同的單詞是不同的Term,不能合并。
		例如:檔案名中包含apache和檔案内容中包含的apache是不同的Term。
		●同名域(Field)的相同單詞是相同的Term,可以合并。
		例如:兩個文檔中都有【檔案名】Field域中都含有Java,這兩個Java就是一個Term(域和單詞都相同)

			例子是以兩個文檔的【content】域作為示範的例子,是以隻要單詞相同就是相同的Term,就可以合并。合并結果如下:合并的同時要記錄這個Term來自于哪個文檔以及出現的次數。

		●Document Frequency(DF):文檔頻次,表示多少文檔出現過此詞(Term)
		●Frequency(TF):詞頻,表示某個文檔中該詞(Term)出現過幾次
		例如:對詞項(Term) “allow”來講,總共有兩篇(DF)文檔包含此Term,Term後面的文檔連結清單總共有兩個,第一個表示包含“allow”的第一篇文檔,即DocId=1的文檔,此文檔中“allow”出現了2次(TF),第二個表示包含“allow”的第二個文檔,即DocId=2的文檔,此文檔中,”allow”出現了1次(TF)。
			索引表 + 文檔倒排連結清單 + 文檔對象集合, 共同組成了索引庫
			●索引表是儲存索引詞項的
			●文檔倒排連結清單是儲存包含詞項的文檔ID的
			●文檔對象集合是儲存文檔具體内容的
		3.2.5.索引流程總結


		3.3.查詢索引
		查詢索引就是搜尋的過程。搜尋就是使用者輸入關鍵字,從索引(index)中進行搜尋的過程。根據關鍵字搜尋索引,根據索引找到對應的文檔,進而找到要搜尋的内容。
		3.3.1.使用者
		使用者可以是自然人,也可以是遠端調用的程式。
		3.3.2.使用者搜尋界面
		(手動程式設計)
		搜尋界面用于送出使用者搜尋關鍵字的,也相當于采集資料的作用。
		比如:

		注意:Lucene不提供制作使用者搜尋界面的功能,需要根據自己的需求開發搜尋界面。
		3.3.3.分析使用者搜尋關鍵字
		(手動調用由Lucene自帶的或第三方提供的解析器完成)
		此處的分析過程跟索引流程中的分析文檔對象的過程必須要一緻。經過分析最終形成詞項Term,隻不過這個Term還缺少是屬于那個Field域的部分。
		分析過程對于索引流程和查詢索引流程要一緻的原因是,如果不一緻會造成兩邊最終的分詞結果不同,這樣會什麼也搜尋不到的。
		3.3.4.建立查詢對象
		(手動new或手動調用解析器生成)
		給上面的Term指定Field域,在實際應用的時候,使用者查詢時是沒有要指定Field域的地方,那我們該如何搜尋呢?所有的搜尋服務都存在一個預設域,預設域是将多個已知Field合并并優化的Field,是以查詢這個預設Field域的效率會更高。
		比如上面的淘寶的站内搜尋,假設需要對商品名稱Field域和商品描述Field域進行關鍵字的查詢,就可以将這兩個Field合并成一個新的Field域,并将這個新的Field域指定成預設域,具體的合并過程Lucene中不進行學習,我們會展Solr中學習使用。
		例如:預設域名為product_keywords,那麼會在Lucene内部形成一個查詢對象Query,在Query對象内部會生成查詢語句:“product_keywords:台燈”。
		建立查詢對象在明天會詳細講解。
		3.3.5.執行查詢
		(Lucene自動完成)
		比如,在淘寶頁面上查詢台燈關鍵字,選擇過濾條件:光源類型為LED,開關類型為調光開關以外,建立查詢對象後實際生成的查詢語句是:
		product_keywords:台燈 AND product_keywords:LED NOT product_keywords:調光開關
		Lucene的查詢語句和SQL查詢條件類似,有關鍵字有條件。如果在程式中調用Lucene全文檢索服務時,可以在程式中直接寫類似上面的查詢語句的,就好我們在JDBC程式中寫SQL是一樣的作用。

		對條件進行解析并執行查詢:(三步)
		●第一步:對查詢語句進行詞法分析、文法分析及語言處理
		1. 詞法分析
			如上述例子中,經過詞法分析,得到單詞有台燈,LED,調光開關, 關鍵字有AND, NOT。
			注意:關鍵字必須大寫,否則就作為普通單詞處理。關鍵字有AND、OR、NOT。

		2. 文法分析
		如果發現查詢語句不滿足文法規則,則會報錯。如product_keywords:台燈 NOT AND 調光開關,則會出錯。
		如果查詢語句滿足文法規則,就會形成文法樹如下:
		 
		3. 語言處理
			如LED變成led等。
			經過第三步,我們得到一棵經過語言處理的文法樹。
				

		●第二步:搜尋索引,得到符合文法樹的文檔
		1. 首先,在反向索引表中,分别找出包含lucene,learn,hadoop的文檔連結清單。


		2. 其次,對包含lucene,learn的連結清單進行合并操作,得到既包含lucene又包含learn的文檔連結清單。


		3. 然後,将此連結清單與hadoop的文檔連結清單進行差操作,去除包含hadoop的文檔,進而得到既包含lucene又包含learn而且不包含hadoop的文檔連結清單。


		4. 此文檔連結清單就是我們要找的文檔。


		●第三步:根據得到的文檔和查詢語句的相關性,對結果進行排序
			(Lucene自動計算排序,明天會講相關性排序)

		3.3.6.渲染結果
		以一個友好的界面将查詢結果展示給使用者,使用者根據搜尋結果找自己想要的資訊,為了幫助使用者很快找到自己的結果,提供了很多展示的效果,比如搜尋結果中将關鍵字高亮顯示,百度提供的快照等。




		3.4.總結
		綜上,采集來的原始資料經過分析處理形成了索引庫,通過查詢條件查詢索引表可以得到相關的Term詞項,由此從該Term關聯的文檔倒排連結清單中得到在Document對象集合中的定位資訊(DocId),然後通過DocId就可以從Document集合中得到相關的Document對象,最終可以從Document對象的指定Field域中取值傳回給使用者。
		4.配置開發環境
		4.1.Lucene下載下傳
		Lucene是開發全文檢索功能的工具包,從官方網站下載下傳Lucene4.10.3,并解壓。
		官方網站:http://lucene.apache.org/ 
		版本:lucene4.10.3
		Jdk要求:1.7以上
		IDE:Eclipse
		4.2.使用的jar包
		下載下傳後的壓縮包解壓縮:


		Lucene基本開發jar包:
		lucene-core-4.10.3.jar
		lucene-analyzers-common-4.10.3.jar
		lucene-queryparser-4.10.3.jar

			1) lucene-core-4.10.3.jar的位置:這是Lucene的核心jar包


		2) lucene-analyzers-common-4.10.3.jar的位置:這是Lucene的分析器的核心jar包


		3) lucene-queryparser-4.10.3.jar的位置:這是Lucene的查詢解析器jar包
			
		其它:用于處理檔案内容的工具包
		commons-io-2.4.jar

		4.3.建立java工程
		建立一個java工程,編碼格式utf-8,并導入jar包并導入Junit測試的jar。
		5.入門程式
		5.1.需求
		實作一個檔案的搜尋功能,通過關鍵字搜尋檔案,凡是檔案名或檔案内容包括關鍵字的檔案都需要找出來。還可以根據中文詞語進行查詢,并且需要支援多個條件查詢。
		本案例中的原始内容就是磁盤上的檔案,如下圖:

		這裡我們要搜尋的文檔是磁盤上的文本檔案,我們要把凡是檔案名或檔案内容中包括關鍵字的檔案都要找出來,是以這裡要對檔案名和檔案内容建立索引。




		本案例我們要擷取磁盤上檔案的内容,可以通過檔案流來讀取文本檔案的内容,對于pdf、doc、xls等檔案可通過第三方提供的解析工具讀取檔案内容,比如Apache POI讀取doc和xls的檔案内容。

		使用IndexWriter的對象建立索引。
		5.2.建立索引
		5.2.1.實作步驟
		第一步:建立IndexWriter對象(建立索引的準備工作)
				1)指定索引庫的存放位置Directory對象
				2)建立一個分析器,對document對象中Field域的内容進行分析
				3)建立一個IndexWriterConfig對象,用于配置建立索引所需的資訊
					參數1:Lucene的版本(可以選擇對應的版本,也可以選擇LATEST)
					參數2:分析器對象
				4)根據Directory對象和IndexWriterConfig對象建立IndexWriter對象
		第二步:開始建立索引
				1)采集原始資料
				2)建立document對象
					根據業務需求建立Field域對象來儲存原始資料中的各部分内容
					(參數1:域名、參數2:域值、參數3:是否存儲)
					把上面建立好的Field對象添加進document對象中。
				3)用IndexWriter對象建立索引
					(添加過程:用IndexWriter對象添加并分析文檔對象,然後建立索引,并寫入索引庫)
		第三步:關閉IndexWriter對象(關閉中帶有送出處理)
		5.2.2.代碼實作
		【CreateIndexTest.java】
		package cn.baidu.test;
		import java.io.File;
		import org.apache.commons.io.FileUtils;
		import org.apache.lucene.analysis.Analyzer;
		import org.apache.lucene.analysis.standard.StandardAnalyzer;
		import org.apache.lucene.document.Document;
		import org.apache.lucene.document.Field.Store;
		import org.apache.lucene.document.TextField;
		import org.apache.lucene.index.IndexWriter;
		import org.apache.lucene.index.IndexWriterConfig;
		import org.apache.lucene.store.Directory;
		import org.apache.lucene.store.FSDirectory;
		import org.apache.lucene.util.Version;
		import org.junit.Test;

		public class CreateIndexTest {
			/**
			 * 建立IndexWriter(建立索引準備工作)
			 */
			private IndexWriter createIndexWriter(String indexRepositoryPath) throws Exception {
				// 建立Directory對象
				Directory dir = FSDirectory.open(new File(indexRepositoryPath));
				// 索引庫還可以存放到記憶體中
				// Directory directory = new RAMDirectory();
				// 建立一個标準分析器
				Analyzer analyzer = new StandardAnalyzer();
				// 建立IndexWriterConfig對象
				// 參數1: Lucene的版本資訊, 可以選擇對應的Lucene版本也可以使用LATEST
				// 參數2: 分析器對象
				IndexWriterConfig config = new IndexWriterConfig(Version.LATEST, analyzer);
				// 建立IndexWriter對象
				return new IndexWriter(dir, config);
			} 

			/**
			 * 建立索引
			 */
			@Test
			public void testCreateIndex() throws Exception {
				// 第一步:建立IndexWriter(建立索引的準備工作)
				IndexWriter indexWriter = createIndexWriter("C:\\mydir\\03_workspace\\lucene\\index");
				// 第二步:開始建立索引
				// 采集原始資料(從指定的目錄下取得檔案對象清單集合)
				File dirSource = new File("C:\\mydir\\03_workspace\\searchsource");
				// 周遊檔案對象清單
				for (File f : dirSource.listFiles()) {
					// 檔案名
					String fileName = f.getName();
					// 檔案内容
					String fileContent = FileUtils.readFileToString(f, "utf-8");
					// 檔案路徑
					String filePath = f.getPath();
					// 檔案大小
					long fileSize = FileUtils.sizeOf(f);
					// 建立檔案名域: 參數1:域的名稱, 參數2:域的内容, 參數3:是否存儲
					TextField fileNameField = new TextField("filename", fileName, Store.YES);
					// 建立檔案内容域
					TextField fileContentField = new TextField("content", fileContent, Store.YES);
					// 建立檔案路徑域
					TextField filePathField = new TextField("path", filePath, Store.YES);
					// 建立檔案大小域
					TextField fileSizeField = new TextField("size", String.valueOf(fileSize), Store.YES);
					
					// 建立document對象
					Document document = new Document();
					document.add(fileNameField);
					document.add(fileContentField);
					document.add(filePathField);
					document.add(fileSizeField);
					
					// 建立索引(用indexWriter對象)
					indexWriter.addDocument(document);
				}
				// 第三步:關閉indexWriter
				indexWriter.close();
			}
		}
		執行效果:
		在檔案夾【C:\mydir\03_workspace\lucene\index】中出現了以下檔案,表示建立索引成功

		5.2.3.使用Luke工具檢視索引檔案
		使用luke工具。Luke是一個便于使用Lucene開發和診斷的第三方工具,它可以通路現有利用Lucene建立的索引,并允許顯示和修改。
		1.啟動工具:直接輕按兩下【start.bat】或者在控制台輸入【java -jar lukeall-4.10.3.jar】

		2.選擇索引庫位置

		3. 索引域的展示效果:

		4. 文檔域的展示效果:

		5.3.查詢索引
		5.3.1.實作步驟
		第一步:查詢準備工作(建立IndexReader、IndexSearcher對象)
				1)指定索引庫的存放位置Directory對象
				2)根據Directory對象建立IndexReader對象
				3)根據IndexReader對象建立IndexSearcher對象
		第二步:建立查詢條件對象(建立一個Term的精确查詢——方式一)
		第三步:執行查詢(參數1:查詢條件對象,參數2:查詢結果傳回的最大值)
		第四步:處理查詢結果
		1)輸出結果數量
		2)周遊查詢結果并輸出
		第五步:關閉IndexReader對象
		5.3.2.代碼實作
			// 查詢索引
			@Test
			public void testSearchIndex() throws Exception {
				// 第一步:查詢準備工作
				// 建立Directory對象
				Directory dir = FSDirectory.open(new File("C:\\mydir\\03_workspace\\lucene\\index"));
				// 建立IndexReader對象
				IndexReader reader = DirectoryReader.open(dir);
				// 建立IndexSearcher對象
				IndexSearcher searcher = new IndexSearcher(reader);
				// 第二步:建立查詢條件對象
				// 手動建立查詢對象時是沒有指定任何分析器的, 是以手動建立的查詢對象沒有分析查詢語句的能力,
				// 隻能設定什麼就查什麼, 而且指定什麼就查詢什麼
				TermQuery query = new TermQuery(new Term("filename", "apache"));
				// 第三步:執行查詢(參數1:查詢條件對象,參數2:查詢結果傳回的最大值)
				TopDocs topDocs = searcher.search(query, 10); 
				// 第四步:處理查詢結果
				// 輸出結果數量
				System.out.println("查詢的結果數量:" + topDocs.totalHits);
				// 取得結果集
				// 這個就是查詢索引的結果,但是這個裡面沒有具體的内容,
				// 隻是關于檔案名中包含apache的檔案的文檔ID和具體相關度的計算結果值
				// 要想取得檔案詳細内容可以根據文檔ID,利用IndexSearcher對象查詢
				ScoreDoc[] scoreDocs = topDocs.scoreDocs;
				// 周遊結果集
				for (ScoreDoc scoreDoc : scoreDocs) {
					// 根據文檔對象ID取得文檔對象
					Document doc = searcher.doc(scoreDoc.doc);
					// 列印搜尋結果内容
					// 檔案名稱
					System.out.println("檔案名稱:" + doc.get("filename"));
					// 檔案路徑
					System.out.println("檔案路徑:" + doc.get("path"));
					// 檔案大小
					System.out.println("檔案大小:" + doc.get("size"));
				}
				// 關閉IndexReader對象
				reader.close();
			}

		5.3.3.TopDocs
		Lucene搜尋結果可通過TopDocs周遊,TopDocs類提供了少量的屬性,如下:

		方法或屬性	說明
		totalHits	比對搜尋條件的總記錄數
		scoreDocs	頂部比對記錄
		注意:
		Search方法需要指定比對記錄數量n:indexSearcher.search(query, n)
		TopDocs.totalHits:是比對索引庫中所有記錄的數量
		TopDocs.scoreDocs:相關度排名靠前的記錄數組,scoreDocs的長度小于等于search方法指定的參數n
		6.分析器
		分析器是索引的關鍵,如果分詞分不好,建立出來的索引根本沒有任何意義沒法使用。是以我們來認識一下分析器。
		6.1.分析器(Analyzer)的執行過程
		一個分析器就是一個管道,其中由一個分詞器對象 + 若幹個過濾器對象串行組成。輸入的内容經過逐層過濾最終分解成語彙單元Token,如下圖是語彙單元的生成過程:

		Token是分析器的直接産物。Token本身也是一個對象,它裡面也包含了一些關于這個詞的重要資訊。
		Token對象詳細内容:
			·詞語本身内容
			·在目前這段話(Field域值中儲存的)中開始位置、結束位置
			·一個可以存儲其他雜項資訊的payload對象(忽略)。
		6.2.分析器的分詞效果
		如果想要看看分析器的分析效果,隻需要看TokenStream中的内容就可以了。每個分析器都有一個方法tokenStream,傳回一個tokenStream對象:
			//檢視标準分析器的分詞效果
			@Test
			public void testTokenStream() throws Exception {
				//建立一個标準分析器對象
				Analyzer analyzer = new StandardAnalyzer();
				//獲得tokenStream對象
				//第一個參數:域名,可以随便給一個
				//第二個參數:要分析的文本内容
				TokenStream tokenStream = analyzer.tokenStream("test", "The Spring Framework provides a comprehensive programming and configuration model.");
				//添加一個引用,可以獲得每個關鍵詞
				CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class);
				//添加一個偏移量的引用,記錄了關鍵詞的開始位置以及結束位置
				OffsetAttribute offsetAttribute = tokenStream.addAttribute(OffsetAttribute.class);
				//将指針調整到清單的頭部
				tokenStream.reset();
				//周遊關鍵詞清單,通過incrementToken方法判斷清單是否結束
				while(tokenStream.incrementToken()) {
					//關鍵詞的起始位置
					System.out.println("start->" + offsetAttribute.startOffset());
					//取關鍵詞
					System.out.println(charTermAttribute);
					//結束位置
					System.out.println("end->" + offsetAttribute.endOffset());
				}
				tokenStream.close();
			}

		6.3.中文分析器
		對于分詞來說,不同的語言,分詞規則是不同的,比如英語每個單詞都是用空格分隔,是以拆分詞的規則比較簡單,我們可以簡單以空格判斷某個字元串是否為一個單詞,比如I love China,love 和 China很容易被程式區分開來。
		漢字就不同了,中文是以字為機關的,字組成詞,字和詞再組成句子。是以它的詞必須根據語義分析後才能正确的拆分,是以拆分詞的規則會很複雜。比如:“我愛中國”,電腦不知道“中國”是一個詞語還是“愛中”是一個詞語。把中文的句子切分成有意義的詞就是中文分詞,也稱切詞。“我愛中國”,正确的分詞結果是:我、愛、中國。
		6.3.1.Lucene自帶中文分析器
		StandardAnalyzer:
		單字分詞:就是按照中文一個字一個字地進行分詞。如:“我愛中國”,
		效果:“我”、“愛”、“中”、“國”。
		CJKAnalyzer
		二分法分詞:按兩個字進行切分。如:“我是中國人”,效果:“我是”、“是中”、“中國”“國人”。

		上邊兩個分詞器無法滿足需求。
		SmartChineseAnalyzer
		對中文支援較好,但擴充性差,擴充詞庫,禁用詞庫和同義詞庫等不好處理
		6.3.2.第三方中文分析器
		  paoding: 庖丁解牛最新版在 https://code.google.com/p/paoding/ 中最多支援Lucene 3.0,且最新送出的代碼在 2008-06-03,在svn中最新也是2010年送出,已經過時,不予考慮。
		  mmseg4j:最新版已從 https://code.google.com/p/mmseg4j/ 移至 https://github.com/chenlb/mmseg4j-solr,支援Lucene 4.10,且在github中最新送出代碼是2014年6月,從09年~14年一共有:18個版本,也就是一年幾乎有3個大小版本,有較大的活躍度,用了mmseg算法。
		  IK-analyzer: 最新版在https://code.google.com/p/ik-analyzer/上,支援Lucene 4.10從2006年12月推出1.0版開始, IKAnalyzer已經推出了4個大版本。最初,它是以開源項目Luence為應用主體的,結合詞典分詞和文法分析算法的中文分詞元件。從3.0版本開 始,IK發展為面向Java的公用分詞元件,獨立于Lucene項目,同時提供了對Lucene的預設優化實作。在2012版本中,IK實作了簡單的分詞 歧義排除算法,标志着IK分詞器從單純的詞典分詞向模拟語義分詞衍化。 但是也就是2012年12月後沒有在更新。
		  ansj_seg:最新版本在 https://github.com/NLPchina/ansj_seg tags僅有1.1版本,從2012年到2014年更新了大小6次,但是作者本人在2014年10月10日說明:“可能我以後沒有精力來維護ansj_seg了”,現在由”nlp_china”管理。2014年11月有更新。并未說明是否支援Lucene,是一個由CRF(條件随機場)算法所做的分詞算法。
		  imdict-chinese-analyzer:最新版在 https://code.google.com/p/imdict-chinese-analyzer/ , 最新更新也在2009年5月,下載下傳源碼,不支援Lucene 4.10 。是利用HMM(隐馬爾科夫鍊)算法。
		  Jcseg:最新版本在git.oschina.net/lionsoul/jcseg,支援Lucene 4.10,作者有較高的活躍度。利用mmseg算法。
		6.4.中文分析器——IKAnalyzer


		使用方法:
		第一步:把jar包添加到工程中
		第二步:把配置檔案和擴充詞典和停用詞詞典添加到classpath下

		注意:mydict.dic和ext_stopword.dic檔案的格式為UTF-8,注意是無BOM 的UTF-8 編碼。

		使用EditPlus.exe儲存為無BOM 的UTF-8 編碼格式,如下圖:

		6.4.1.添加jar包
		在【資料\jar\IK】下找到IKAnalyzer的jar包

		6.4.2.修改代碼
		IKAnalyzer繼承Lucene的Analyzer抽象類,使用IKAnalyzer和Lucene自帶的分析器方法一樣,将建立索引的測試代碼中的【StandardAnalyzer】改為【IKAnalyzer】測試中文分詞效果。
		可以和之前使用StandardAnalyzer分析器建立的索引可以對比一下:
		StandardAnalyzer分析得出的索引結果:

		IKAnalyzer分析得出的索引結果:

		從結果看出IKAnalyzer能更好的從語義上識别中文,并做出比較正确的切分詞。
		6.4.3.擴充詞庫的使用
		IKAnalyzer允許使用者擴充自己的中文詞庫,包括擴充詞庫和停用詞庫。
		擴充詞庫:是把一些特殊的專有名詞加進來,這樣分詞的時候就會把專有名詞當成一個整體,不會被切分。
		停用詞庫:是把一些想過濾掉的詞加進來,這樣分詞後就會被過濾器過濾掉,不作為索引的語彙單元。
		6.4.3.1.擴充詞庫檔案與停用詞庫檔案
		下載下傳下來的IK壓縮包中可能有停用詞庫,但沒有擴充詞庫,但可以手動建立,但要注意:在建立詞庫時,不要用windows自帶的記事本儲存詞庫檔案,因為windows預設格式是含有bom檔案頭的,這是個不可見檔案辨別符号,IK識别的時候會出錯,因為非windows系統都是不帶bom檔案頭的。

		擴充詞庫【ext.dic】
		程式設計思想
		傳智播客

		停用詞庫【stopword.dic】
		a
		an
		and
		are
		as
		at
		be
		but
		by
		for
		if
		in
		into
		is
		it
		no
		not
		of
		on
		or
		such
		that
		the
		their
		then
		there
		these
		they
		this
		to
		was
		will
		with
		地
		的
		得
		自帶的沒有【的】【地】【得】,給它加進去。
		6.4.3.2.配置詞庫
		【IKAnalyzer.cfg.xml】配置檔案
		<?xml version="1.0" encoding="UTF-8"?>
		<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">  
		<properties>  
			<comment>IK Analyzer 擴充配置</comment>
			<!--使用者可以在這裡配置自己的擴充字典 -->
			<entry key="ext_dict">ext.dic;</entry> 
			
			<!--使用者可以在這裡配置自己的擴充停止詞字典-->
			<entry key="ext_stopwords">stopword.dic;</entry> 
		</properties>
		把詞庫檔案和配置檔案都放到工程的config下:

		6.4.3.3.測試
		為了便于測試結果的确認,在資料庫book表中把每一條記錄的description中都加入:【《計算機科學叢書:Java程式設計思想(第4版)》【傳智播客】】這一段話,這樣可以增加【程式設計思想】和【傳智播客】的出現頻率,搜尋排名會靠前。
		1.不加擴充詞庫和停用詞庫時建立索引的結果:
		停用詞沒有被過濾掉:and,的,the等都被加進了索引庫
		擴充詞【程式設計思想】【傳值播客】被分開了


		2.添加停用詞庫後重新建立索引(将原來的索引檔案删除,注意:要先關閉Luke)

		如果加入log4j,再次運作的log:

		已經看不到被停用的單詞了:


		3.添加擴充詞庫後重新建立索引(将原來的索引檔案删除,注意:要先關閉Luke)

		再次運作的log:

		已經看到擴充詞沒有被切分:
		【傳值播客】是純粹的專有名詞,是以完全的被保留,沒有切分
		【程式設計思想】并不是純粹的專有名詞,在IK的内部的中文分詞器中仍然會識别【程式設計】和【思想】,然後你又追加了【程式設計思想】,是以最終是三個詞【程式設計】【思想】【程式設計思想】

		6.5.分析器Analyzer使用時機
		6.5.1.索引時使用的Analyzer
			建立索引時對文檔對象的内容進行分析是一個必要的過程,大部分文檔内容都是需要被分析的,但也有一些特殊的Field域的内容是不用分析,可以直接作為Term建立索引。
		對于一些Field可以不用分析:
		1、不作為查詢條件的内容,比如檔案路徑
		2、不是比對内容中的詞而比對Field的整體内容,比如訂單号、身份證号等。


		6.5.2.搜尋時使用Analyzer
		使用者輸入的查詢内容也需要進行分析,這個過程和建立索引時的分析是一樣的,是以他們必須使用一緻的分析器對象,否則會出現雙方分析出來的Term對應不上,這樣就無法進行查詢了。
		注意:搜尋使用的分析器要和索引使用的分析器一緻。
			和索引時一樣,查詢是也存在一些特殊的查詢是不需要分析的,比如根據訂單号、身份證号查詢等。	           

複制