2.信息檢索
信息檢索是計算機世界中很是重要的一種功能。信息檢索不只僅是指從數據庫檢索數據,還包括從文件、網頁、郵件、用戶手輸入的內容中檢索數據。經過怎樣的高效方式將用戶想要的信息快速提取出來,是計算機技術人員研究的重點方向之一。
2.1.數據分類
咱們生活中的數據整體分爲兩種:結構化數據和非結構化數據。
結構化數據:指具備固定格式或有限長度的數據,如數據庫,元數據等。
非結構化數據:指不定長或無固定格式的數據,如郵件,word文檔等磁盤上的文件
2.2.結構化數據搜索方法
數據庫就是最多見的結構化數據。經過SQL能夠很是方便的查詢數據。
爲何數據庫中的數據能很是方便的搜索出來?
由於數據庫中的數據存儲在表中,表有行有列有類型有長度,所以才能夠經過很是方便的SQL查詢結果。也就是說結構化的數據有規律,因此纔好進行查找。
試想一下若是數據沒有進行結構化,沒有任何規律該如何查詢?
2.3.非結構化數據查詢方法
咱們考慮一個小時候學查字典的場景:小時候咱們都使用過新華字典,老師叫你翻開第268頁從268頁到269頁,找到「坑爹」的坑,此時你會怎麼查找?——毫無疑問,你的眼睛會從268頁的第一個字開始從頭到尾地掃描,直到找到「坑」字爲止。這種按照內容的順序一個一個字符的查找方法叫作順序掃描法(Serial Scanning)。對於少許的數據,使用順序掃描是夠用的。html
可是若是老師不告訴你你坑爹的「坑」字在哪一頁呢?也沒有教你如何查字典呢?
你只能從第一頁的第一個字逐個的掃描下去,那樣你真的是被坑了。查找的過程會至關的慢,甚至會讓你崩潰,因此這種坑爹的事情咱們不能去作。咱們要從新思考此時的查詢辦法。java
思考一下新華字典是怎麼解決漢字的快速查找的?
從一堆沒有結構的內容中提取出來文字的位置信息(頁碼)、文字寫法(文字自己)、漢語拼音,而後將它們從新整理、排序、概括,最終造成一張結構化的表,咱們叫作漢語拼音音節索引表。
漢語拼音索引中記錄了「坑」字在哪一頁的信息,只要你知道「坑」字的漢語拼音,就能夠快速的查找到「坑」字在哪,這樣答案就出來了。
下圖是漢語拼音音節索引表:
git
重新華字典的例子總結一下,如何從一堆沒有規律沒有結構的信息中快速的查找咱們須要的信息?
最有效的方法就是先將信息從新組織(提取、整理、排序、概括),造成新的集合(即一個更方便更高效查找的集合),而後查詢這個結構化的集合,從中找出你要找的信息在原文中的位置。
簡單概括成:
非結構化結構化保存結構化
查找結構化獲得在非結構化中的定位github
這部分從非結構化數據中提取出來,從新組織的結構化信息,咱們稱之索引。
這種先對全文創建索引集合,再對索引集合進行檢索的查詢方式就叫全文檢索(Full-text Search)。
建立索引的過程會不會很繁瑣費時?
是的,很繁瑣費時間,可是值得的,由於索引一旦建立就能夠屢次使用,最終能夠帶來高效的查詢速度,是一件一勞永逸的事情。
2.4.如何實現全文檢索
Apache提供了一個開源的全文檢索開發框架——Lucene。它提供了完整的查詢模塊和索引模塊,利用這些核心模塊,開發人員能夠方便、快速的開發出全文檢索應用。
根據上面的簡單概括能夠知道咱們須要使用Lucene要作兩件事情:
非結構化結構化保存結構化————建立索引
查找結構化獲得在非結構化中是定位————查詢索引web
注意:索引的數據來源不只僅侷限於數據庫,一切能夠採集的數據均可以被創建索引。
2.5.系統的數據查詢方案算法
·基本的數據查詢方案在面對查詢量大的應用時會對數據庫形成極大的壓力,並且查詢效率會很低。
·改進後的數據查詢方案將讀寫進行了分離,將查詢量大的應用的查詢請求分發給了索引庫,查詢直接走索引庫,不走數據庫,這樣就有效的下降了數據庫的壓力,同時索引庫查詢的高效特性也可以保證查詢效率。
2.6.全文檢索的應用場景
全文檢索應用最多的就是開發站內搜索服務。尤爲是對於電商系統,大數據量的搜索都是使用的站內搜索服務。
還有專業的搜索引擎中也有全文檢索技術的使用,好比百度、Google等,但專業的搜索引擎不僅使用這一種搜索技術。
3.Lucene實現全文檢索的流程
3.1.建立索引和查詢索引流程數據庫
說明:
1.綠色表示建立索引過程,包括:
採集數據構建文檔對象分析文檔對象建立索引(保存到索引庫)apache
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.採集數據windows
(手動編程)
從互聯網上、數據庫、文件系統中等獲取須要搜索的原始信息,這個過程就是信息採集,信息採集的目的是爲了對原始內容進行索引。
如何採集數據?
一、互聯網上的網頁:可使用工具將網頁抓取到本地生成html文件。
二、數據庫中的數據:能夠直接鏈接數據庫用SQL查詢數據。
三、文件系統中的文件:能夠經過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能夠不用分析:
一、不做爲查詢條件的內容,好比文件路徑
二、不是匹配內容中的詞而匹配Field的總體內容,好比訂單號、身份證號等。
6.5.2.搜索時使用Analyzer 用戶輸入的查詢內容也須要進行分析,這個過程和建立索引時的分析是同樣的,所以他們必須使用一致的分析器對象,不然會出現雙方分析出來的Term對應不上,這樣就沒法進行查詢了。 注意:搜索使用的分析器要和索引使用的分析器一致。 和索引時同樣,查詢是也存在一些特殊的查詢是不須要分析的,好比根據訂單號、身份證號查詢等。