lucene原理及java實現【容易理解】

轉自:http://blog.csdn.net/liuhaiabc/article/details/52346493

1、Lucene是什麼?

Lucene 是一個高效的,基於Java 的全文檢索庫。html

因此在瞭解Lucene以前要費一番工夫瞭解一下全文檢索。java

那麼什麼叫作全文檢索呢?這要從咱們生活中的數聽說起。程序員

咱們生活中的數據整體分爲兩種:結構化數據 和非結構化數據 。算法

  • 結構化數據: 指具備固定格式或有限長度的數據,如數據庫,元數據等。
  • 非結構化數據: 指不定長或無固定格式的數據,如郵件,word文檔等。

固然有的地方還會提到第三種,半結構化數據,如XML,HTML等,當根據須要可按結構化數據來處理,也可抽取出純文本按非結構化數據來處理。數據庫

非結構化數據又一種叫法叫全文數據。apache

 

按照數據的分類,搜索也分爲兩種:windows

  • 對結構化數據的搜索 :如對數據庫的搜索,用SQL語句。再如對元數據的搜索,如利用windows搜索對文件名,類型,修改時間進行搜索等。
  • 對非結構化數據的搜索 :如利用windows的搜索也能夠搜索文件內容,Linux下的grep命令,再如用Google和百度能夠搜索大量內容數據。

對非結構化數據也即對全文數據的搜索主要有兩種方法:app

一種是順序掃描法 (Serial Scanning): 所謂順序掃描,好比要找內容包含某一個字符串的文件,就是一個文檔一個文檔的看,對於每個文檔,從頭看到尾,若是此文檔包含此字符串,則此文檔爲咱們要找的文件,接着看下一個文件,直到掃描完全部的文件。如利用windows的搜索也能夠搜索文件內容,只是至關的慢。若是你有一個80G硬盤,若是想在上面找到一個內容包含某字符串的文件,不花他幾個小時,怕是作不到。Linux下的grep命令也是這一種方式。你們可能以爲這種方法比較原始,但對於小數據量的文件,這種方法仍是最直接,最方便的。可是對於大量的文件,這種方法就很慢了。ide

有人可能會說,對非結構化數據順序掃描很慢,對結構化數據的搜索卻相對較快(因爲結構化數據有必定的結構能夠採起必定的搜索算法加快速度),那麼把咱們的非結構化數據想辦法弄得有必定結構不就好了嗎?oop

這種想法很自然,卻構成了全文檢索的基本思路,也即將非結構化數據中的一部分信息提取出來,從新組織,使其變得有必定結構,而後對此有必定結構的數據進行搜索,從而達到搜索相對較快的目的。

這部分從非結構化數據中提取出的而後從新組織的信息,咱們稱之索引 。

這種說法比較抽象,舉幾個例子就很容易明白,好比字典,字典的拼音表和部首檢字表就至關於字典的索引,對每個字的解釋是非結構化的,若是字典沒有音節表和部首檢字表,在茫茫辭海中找一個字只能順序掃描。然而字的某些信息能夠提取出來進行結構化處理,好比讀音,就比較結構化,分聲母和韻母,分別只有幾種能夠一一列舉,因而將讀音拿出來按必定的順序排列,每一項讀音都指向此字的詳細解釋的頁數。咱們搜索時按結構化的拼音搜到讀音,而後按其指向的頁數,即可找到咱們的非結構化數據——也即對字的解釋。

 

這種先創建索引,再對索引進行搜索的過程就叫全文檢索(Full-text Search) 。

 

下面這幅圖來自《Lucene in action》,但卻不只僅描述了Lucene的檢索過程,而是描述了全文檢索的通常過程。

[圖]全文檢索的通常過程

 

 

全文檢索大致分兩個過程,索引建立 (Indexing) 和搜索索引 (Search) 。

  • 索引建立:將現實世界中全部的結構化和非結構化數據提取信息,建立索引的過程。
  • 搜索索引:就是獲得用戶的查詢請求,搜索建立的索引,而後返回結果的過程。

因而全文檢索就存在三個重要問題:

1. 索引裏面究竟存些什麼?(Index)

2. 如何建立索引?(Indexing)

3. 如何對索引進行搜索?(Search)

下面咱們順序對每一個個問題進行研究。

 

2、索引裏面究竟存些什麼

索引裏面究竟須要存些什麼呢?

首先咱們來看爲何順序掃描的速度慢:

實際上是因爲咱們想要搜索的信息和非結構化數據中所存儲的信息不一致形成的。

非結構化數據中所存儲的信息是每一個文件包含哪些字符串,也即已知文件,欲求字符串相對容易,也便是從文件到字符串的映射。而咱們想搜索的信息是哪些文件包含此字符串,也即已知字符串,欲求文件,也即從字符串到文件的映射。二者偏偏相反。因而若是索引總可以保存從字符串到文件的映射,則會大大提升搜索速度。

因爲從字符串到文件的映射是文件到字符串映射的反向過程,因而保存這種信息的索引稱爲反向索引 。

反向索引的所保存的信息通常以下:

假設個人文檔集合裏面有100篇文檔,爲了方便表示,咱們爲文檔編號從1到100,獲得下面的結構

[圖]字典及倒排表結構

 

左邊保存的是一系列字符串,稱爲詞典 。

每一個字符串都指向包含此字符串的文檔(Document)鏈表,此文檔鏈表稱爲倒排表 (Posting List)。

有了索引,便使保存的信息和要搜索的信息一致,能夠大大加快搜索的速度。

好比說,咱們要尋找既包含字符串「lucene」又包含字符串「solr」的文檔,咱們只須要如下幾步:

1. 取出包含字符串「lucene」的文檔鏈表。

2. 取出包含字符串「solr」的文檔鏈表。

3. 經過合併鏈表,找出既包含「lucene」又包含「solr」的文件。

[圖]倒排表合併過程

 

看到這個地方,有人可能會說,全文檢索的確加快了搜索的速度,可是多了索引的過程,二者加起來不必定比順序掃描快多少。的確,加上索引的過程,全文檢索不必定比順序掃描快,尤爲是在數據量小的時候更是如此。而對一個很大量的數據建立索引也是一個很慢的過程。

然而二者仍是有區別的,順序掃描是每次都要掃描,而建立索引的過程僅僅須要一次,之後即是一勞永逸的了,每次搜索,建立索引的過程沒必要通過,僅僅搜索建立好的索引就能夠了。

這也是全文搜索相對於順序掃描的優點之一:一次索引,屢次使用。

 

3、如何建立索引

全文檢索的索引建立過程通常有如下幾步:

第一步:一些要索引的原文檔(Document)。

爲了方便說明索引建立過程,這裏特地用兩個文件爲例:

文件一:Students should be allowed to go out with their friends, but not allowed to drink beer.

文件二:My friend Jerry went to school to see his students but found them drunk which is not allowed.

 

第二步:將原文檔傳給分詞器(Tokenizer)。

分詞器(Tokenizer)會作如下幾件事情( 此過程稱爲Tokenize) :

1. 將文檔分紅一個一個單獨的單詞。

2. 去除標點符號。

3. 去除停詞(Stop word) 。

所謂停詞(Stop word)就是一種語言中最普通的一些單詞,因爲沒有特別的意義,於是大多數狀況下不能成爲搜索的關鍵詞,於是建立索引時,這種詞會被去掉而減小索引的大小。

英語中停詞(Stop word)如:「the」,「a」,「this」等。

對於每一種語言的分詞組件(Tokenizer),都有一個停詞(stop word)集合。

 

通過分詞(Tokenizer) 後獲得的結果稱爲詞元(Token) 。

在咱們的例子中,便獲得如下詞元(Token):

「Students」,「allowed」,「go」,「their」,「friends」,「allowed」,「drink」,「beer」,「My」,「friend」,「Jerry」,「went」,「school」,「see」,「his」,「students」,「found」,「them」,「drunk」,「allowed」。

 

第三步:將獲得的詞元(Token)傳給語言處理組件(Linguistic Processor)。

語言處理組件(linguistic processor)主要是對獲得的詞元(Token)作一些同語言相關的處理。

對於英語,語言處理組件(Linguistic Processor) 通常作如下幾點:

1. 變爲小寫(Lowercase) 。

2. 將單詞縮減爲詞根形式,如「cars 」到「car 」等。這種操做稱爲:stemming 。

3. 將單詞轉變爲詞根形式,如「drove 」到「drive 」等。這種操做稱爲:lemmatization 。

 

Stemming 和 lemmatization的異同:

  • 相同之處:Stemming和lemmatization都要使詞彙成爲詞根形式。
  • 二者的方式不一樣:
    • Stemming採用的是「縮減」的方式:「cars」到「car」,「driving」到「drive」。
    • Lemmatization採用的是「轉變」的方式:「drove」到「drove」,「driving」到「drive」。
  • 二者的算法不一樣:
    • Stemming主要是採起某種固定的算法來作這種縮減,如去除「s」,去除「ing」加「e」,將「ational」變爲「ate」,將「tional」變爲「tion」。
    • Lemmatization主要是採用保存某種字典的方式作這種轉變。好比字典中有「driving」到「drive」,「drove」到「drive」,「am, is, are」到「be」的映射,作轉變時,只要查字典就能夠了。
  • Stemming和lemmatization不是互斥關係,是有交集的,有的詞利用這兩種方式都能達到相同的轉換。

 

語言處理組件(linguistic processor)的結果稱爲詞(Term) 。

在咱們的例子中,通過語言處理,獲得的詞(Term)以下:

「student」,「allow」,「go」,「their」,「friend」,「allow」,「drink」,「beer」,「my」,「friend」,「jerry」,「go」,「school」,「see」,「his」,「student」,「find」,「them」,「drink」,「allow」。

 

也正是由於有語言處理的步驟,才能使搜索drove,而drive也能被搜索出來。

 

第四步:將獲得的詞(Term)傳給索引組件(Indexer)。

索引組件(Indexer)主要作如下幾件事情:

1. 利用獲得的詞(Term)建立一個字典。

在咱們的例子中字典以下:

Term Document ID
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

 

2. 對字典按字母順序進行排序。

 

Term Document ID
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. 合併相同的詞(Term) 成爲文檔倒排(Posting List) 鏈表。

[圖]倒排表結構

 

在此表中,有幾個定義:

  • Document Frequency 即文檔頻次,表示總共有多少文件包含此詞(Term)。
  • Frequency 即詞頻率,表示此文件中包含了幾個此詞(Term)。

因此對詞(Term) 「allow」來說,總共有兩篇文檔包含此詞(Term),從而詞(Term)後面的文檔鏈表總共有兩項,第一項表示包含「allow」的第一篇文檔,即1號文檔,此文檔中,「allow」出現了2次,第二項表示包含「allow」的第二個文檔,是2號文檔,此文檔中,「allow」出現了1次。

到此爲止,索引已經建立好了,咱們能夠經過它很快的找到咱們想要的文檔。

並且在此過程當中,咱們驚喜地發現,搜索「drive」,「driving」,「drove」,「driven」也可以被搜到。由於在咱們的索引中,「driving」,「drove」,「driven」都會通過語言處理而變成「drive」,在搜索時,若是您輸入「driving」,輸入的查詢語句一樣通過咱們這裏的一到三步,從而變爲查詢「drive」,從而能夠搜索到想要的文檔。

 

 

4、如何對索引進行搜索?

到這裏彷佛咱們能夠宣佈「咱們找到想要的文檔了」。

然而事情並無結束,找到了僅僅是全文檢索的一個方面。不是嗎?若是僅僅只有一個或十個文檔包含咱們查詢的字符串,咱們的確找到了。然而若是結果有一千個,甚至成千上萬個呢?那個又是您最想要的文件呢?

打開Google吧,好比說您想在微軟找份工做,因而您輸入「Microsoft job」,您卻發現總共有22600000個結果返回。好大的數字呀,忽然發現找不到是一個問題,找到的太多也是一個問題。在如此多的結果中,如何將最相關的放在最前面呢?

[圖]Google的搜索實例

 

固然Google作的很不錯,您一下就找到了jobs at Microsoft。想象一下,若是前幾個所有是「Microsoft does a good job at software industry…」將是多麼可怕的事情呀。

如何像Google同樣,在成千上萬的搜索結果中,找到和查詢語句最相關的呢?

如何判斷搜索出的文檔和查詢語句的相關性呢?

這要回到咱們第三個問題:如何對索引進行搜索?

搜索主要分爲如下幾步:

第一步:用戶輸入查詢語句。

查詢語句同咱們普通的語言同樣,也是有必定語法的。

不一樣的查詢語句有不一樣的語法,如SQL語句就有必定的語法。

查詢語句的語法根據全文檢索系統的實現而不一樣。最基本的有好比:AND, OR, NOT等。

舉個例子,用戶輸入語句:lucene AND learned NOT hadoop。

說明用戶想找一個包含lucene和learned然而不包括hadoop的文檔。

第二步:對查詢語句進行詞法分析,語法分析,及語言處理。

因爲查詢語句有語法,於是也要進行語法分析,語法分析及語言處理。

1. 詞法分析主要用來識別單詞和關鍵字。

如上述例子中,通過詞法分析,獲得單詞有lucene,learned,hadoop, 關鍵字有AND, NOT。

若是在詞法分析中發現不合法的關鍵字,則會出現錯誤。如lucene AMD learned,其中因爲AND拼錯,致使AMD做爲一個普通的單詞參與查詢。

2. 語法分析主要是根據查詢語句的語法規則來造成一棵語法樹。

若是發現查詢語句不知足語法規則,則會報錯。如lucene NOT AND learned,則會出錯。

如上述例子,lucene AND learned NOT hadoop造成的語法樹以下:

[圖]語法樹

 

 

3. 語言處理同索引過程當中的語言處理幾乎相同。

如learned變成learn等。

通過第二步,咱們獲得一棵通過語言處理的語法樹。

[圖]通過語言處理的語法樹

 

 

第三步:搜索索引,獲得符合語法樹的文檔。

此步驟有分幾小步:

  1. 首先,在反向索引表中,分別找出包含lucene,learn,hadoop的文檔鏈表。
  2. 其次,對包含lucene,learn的鏈表進行合併操做,獲得既包含lucene又包含learn的文檔鏈表。
  3. 而後,將此鏈表與hadoop的文檔鏈表進行差操做,去除包含hadoop的文檔,從而獲得既包含lucene又包含learn並且不包含hadoop的文檔鏈表。
  4. 此文檔鏈表就是咱們要找的文檔。

第四步:根據獲得的文檔和查詢語句的相關性,對結果進行排序。

雖然在上一步,咱們獲得了想要的文檔,然而對於查詢結果應該按照與查詢語句的相關性進行排序,越相關者越靠前。

如何計算文檔和查詢語句的相關性呢?

不如咱們把查詢語句看做一片短小的文檔,對文檔與文檔之間的相關性(relevance)進行打分(scoring),分數高的相關性好,就應該排在前面。

那麼又怎麼對文檔之間的關係進行打分呢? 這可不是一件容易的事情!

下面看一下如何判斷文檔之間的關係

首先,一個文檔有不少詞(Term)組成 ,如search, lucene, full-text, this, a, what等。

其次對於文檔之間的關係,不一樣的Term重要性不一樣 ,好比對於本篇文檔,search, Lucene, full-text就相對重要一些,this, a , what可能相對不重要一些。因此若是兩篇文檔都包含search, Lucene,fulltext,這兩篇文檔的相關性好一些,然而就算一篇文檔包含this, a, what,另外一篇文檔不包含this, a, what,也不能影響兩篇文檔的相關性。

於是判斷文檔之間的關係,首先找出哪些詞(Term)對文檔之間的關係最重要,如search, Lucene, fulltext。而後判斷這些詞(Term)之間的關係。

 

找出詞(Term) 對文檔的重要性的過程稱爲計算詞的權重(Term weight) 的過程。

計算詞的權重(term weight)有兩個參數,第一個是詞(Term),第二個是文檔(Document)。

詞的權重(Term weight)表示此詞(Term)在此文檔中的重要程度,越重要的詞(Term)有越大的權重(Term weight),於是在計算文檔之間的相關性中將發揮更大的做用。

判斷詞(Term) 之間的關係從而獲得文檔相關性的過程應用一種叫作向量空間模型的算法(Vector Space Model) 。

 

下面仔細分析一下這兩個過程:

1. 計算權重(Term weight)的過程。

影響一個詞(Term)在一篇文檔中的重要性主要有兩個因素:

  • Term Frequency (tf):即此Term在此文檔中出現了多少次。tf 越大說明越重要。
  • Document Frequency (df):即有多少文檔包含次Term。df 越大說明越不重要。

容易理解嗎?詞(Term)在文檔中出現的次數越多,說明此詞(Term)對該文檔越重要,如「搜索」這個詞,在本文檔中出現的次數不少,說明本文檔主要就是講這方面的事的。然而在一篇英語文檔中,this出現的次數更多,就說明越重要嗎?不是的,這是由第二個因素進行調整,第二個因素說明,有越多的文檔包含此詞(Term), 說明此詞(Term)太普通,不足以區分這些文檔,於是重要性越低。

這也如咱們程序員所學的技術,對於程序員自己來講,這項技術掌握越深越好(掌握越深說明花時間看的越多,tf越大),找工做時越有競爭力。然而對於全部程序員來講,這項技術懂得的人越少越好(懂得的人少df小),找工做越有競爭力。人的價值在於不可替代性就是這個道理。

道理明白了,咱們來看看公式:

[圖]權重計算公式

[圖]權重計算公式變量

 

 

 

這僅僅只term weight計算公式的簡單典型實現。實現全文檢索系統的人會有本身的實現,Lucene就與此稍有不一樣。

2. 判斷Term之間的關係從而獲得文檔相關性的過程,也即向量空間模型的算法(VSM)。

咱們把文檔看做一系列詞(Term),每個詞(Term)都有一個權重(Term weight),不一樣的詞(Term)根據本身在文檔中的權重來影響文檔相關性的打分計算。

因而咱們把全部此文檔中詞(term)的權重(term weight) 看做一個向量。

Document = {term1, term2, …… ,term N}

Document Vector = {weight1, weight2, …… ,weight N}

一樣咱們把查詢語句看做一個簡單的文檔,也用向量來表示。

Query = {term1, term 2, …… , term N}

Query Vector = {weight1, weight2, …… , weight N}

咱們把全部搜索出的文檔向量及查詢向量放到一個N維空間中,每一個詞(term)是一維。

如圖:

[圖]向量空間模型

 

 

咱們認爲兩個向量之間的夾角越小,相關性越大。

因此咱們計算夾角的餘弦值做爲相關性的打分,夾角越小,餘弦值越大,打分越高,相關性越大。

有人可能會問,查詢語句通常是很短的,包含的詞(Term)是不多的,於是查詢向量的維數很小,而文檔很長,包含詞(Term)不少,文檔向量維數很大。你的圖中二者維數怎麼都是N呢?

在這裏,既然要放到相同的向量空間,天然維數是相同的,不一樣時,取兩者的並集,若是不含某個詞(Term)時,則權重(Term Weight)爲0。

 

相關性打分公式以下:

[圖]相關性打分的公式

 

 

舉個例子,查詢語句有11個Term,共有三篇文檔搜索出來。其中各自的權重(Term weight),以下表格。

 

t1

t2

t3

t4

t5

t6

t7

t8

t9

t10

t11

D1

0

0

.477

0

.477

.176

0

0

0

.176

0

D2

0

.176

0

.477

0

0

0

0

.954

0

.176

D3

0

.176

0

0

0

.176

0

0

0

.176

.176

Q

0

0

0

0

0

.176

0

0

.477

0

.176

 

因而計算,三篇文檔同查詢語句的相關性打分分別爲:

[圖]文檔一的打分計算

[圖]文檔二的打分計算

[圖]文檔三的打分計算

 

 

 

 

因而文檔二相關性最高,先返回,其次是文檔一,最後是文檔三。

到此爲止,咱們能夠找到咱們最想要的文檔了。

說了這麼多,其實尚未進入到Lucene,而僅僅是信息檢索技術(Information retrieval)中的基本理論,然而當咱們看過Lucene後咱們會發現,Lucene是對這種基本理論的一種基本的的實踐。因此在之後分析Lucene的文章中,會經常看到以上理論在Lucene中的應用。

在進入Lucene以前,對上述索引建立和搜索過程所一個總結,如圖:

此圖參照http://www.lucene.com.cn/about.htm 中文章《開放源代碼的全文檢索引擎Lucene》

[圖]索引建立和搜索過程

 

 

1. 索引過程:

1) 有一系列被索引文件

2) 被索引文件通過語法分析和語言處理造成一系列詞(Term) 。

3) 通過索引建立造成詞典和反向索引表。

4) 經過索引存儲將索引寫入硬盤。

2. 搜索過程:

a) 用戶輸入查詢語句。

b) 對查詢語句通過語法分析和語言分析獲得一系列詞(Term) 。

c) 經過語法分析獲得一個查詢樹。

d) 經過索引存儲將索引讀入到內存。

e) 利用查詢樹搜索索引,從而獲得每一個詞(Term) 的文檔鏈表,對文檔鏈表進行交,差,並獲得結果文檔。

f) 將搜索到的結果文檔對查詢的相關性進行排序。

g) 返回查詢結果給用戶。

5、java實現

目前Lucene已經更新到6.5版本,自從4.9版本就須要1.7以上的JDK,因此若是還用1.6甚至是1.5的小盆友,請參考低版本,因爲我用的1.7,所以在使用Lucene5.5。
    廢話很少說直接上實現的代碼,裏面包含註解:
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import jxl.Cell;
import jxl.Sheet;
import jxl.Workbook;

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.DirectoryReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.poi.POIXMLDocument;
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.usermodel.Range;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;




public class LuceneTest {
private static LuceneTest indexManager;
private static String content="";
private static String INDEX_DIR = "e:\\luceneIndex";
private static String DATA_DIR = "e:\\luceneData";
private static Analyzer analyzer = null;
private static Directory directory = null;
private static IndexWriter indexWriter = null;

public static void main(String[] arg){
File fileIndex = new File(INDEX_DIR);
if(deleteDir(fileIndex)){
           fileIndex.mkdir();
       }else{
           fileIndex.mkdir();
    }
createIndex(DATA_DIR);
    searchIndex("man");
}

    /**
     * 建立當前文件目錄的索引
     * @param path 當前文件目錄
     * @return 是否成功
     */
    public static boolean createIndex(String path){
    
    Date date1 = new Date();
         List<File> fileList = getFileList(path);
         for (File file : fileList) {
        content = "";
             //獲取文件後綴
             String type = file.getName().substring(file.getName().lastIndexOf(".")+1);
             if("txt".equalsIgnoreCase(type)){
                 content += txt2String(file);
             }else if("doc".equalsIgnoreCase(type)){
                 content += doc2String(file);
             }else if("xls".equalsIgnoreCase(type)){
                 content += xls2String(file);
             }
             System.out.println("name :"+file.getName());
             System.out.println("path :"+file.getPath());
             System.out.println("content :"+content);
             System.out.println();
             
             try{
            //詞法分析器Analyzer
            analyzer = new StandardAnalyzer();
            //本地文件存儲
                 directory = FSDirectory.open(Paths.get(INDEX_DIR));
                 //內存存儲
                 //Directory directory = new RAMDirectory();
     
                 File indexFile = new File(INDEX_DIR);
                 if (!indexFile.exists()) {
                     indexFile.mkdirs();
                 }
                 //建立IndexWriter,進行索引文件的寫入
                 IndexWriterConfig config = new IndexWriterConfig(analyzer);
                 indexWriter = new IndexWriter(directory, config);
                 
                 //內容提取,進行索引的存儲。
                 Document document = new Document();
                 document.add(new TextField("filename", file.getName(), Store.YES));
                 document.add(new TextField("content", content, Store.YES));
                 document.add(new TextField("path", file.getPath(), Store.YES));
                 indexWriter.addDocument(document);
                 indexWriter.commit();
                 closeWriter();
             }catch(Exception e){
            e.getStackTrace();
             }
             
         }
         Date date2 = new Date();
         System.out.println("建立索引-----耗時:" + (date2.getTime() - date1.getTime()) + "ms\n");
         return true;
    }
    /**
     * 查找索引,返回符合條件的文件
     * @param text 查找的字符串
     * @return 符合條件的文件List
     */
    public static void searchIndex(String text){
    Date date1 = new Date();
        try{
        //打開存儲位置
            directory = FSDirectory.open(Paths.get(INDEX_DIR));
            analyzer = new StandardAnalyzer();
            DirectoryReader ireader = DirectoryReader.open(directory);
            //建立搜索器
            IndexSearcher isearcher = new IndexSearcher(ireader);
    
            QueryParser parser = new QueryParser("content", analyzer);
            Query query = parser.parse(text);
            
            ScoreDoc[] hits = isearcher.search(query, null, 1000).scoreDocs;
        
            for (int i = 0; i < hits.length; i++) {
                Document hitDoc = isearcher.doc(hits[i].doc);
                System.out.println("____________________________");
                System.out.println(hitDoc.get("filename"));
                System.out.println(hitDoc.get("content"));
                System.out.println(hitDoc.get("path"));
                System.out.println("____________________________");
            }
            ireader.close();
            directory.close();
        }catch(Exception e){
            e.printStackTrace();
        }
        Date date2 = new Date();
        System.out.println("查看索引-----耗時:" + (date2.getTime() - date1.getTime()) + "ms\n");
    }
  /**
     * 刪除文件目錄下的全部文件
     * @param file 要刪除的文件目錄
     * @return 若是成功,返回true.
     */
    public static boolean deleteDir(File file){
        if(file.isDirectory()){
            File[] files = file.listFiles();
            for(int i=0; i<files.length; i++){
                deleteDir(files[i]);
            }
        }
        file.delete();
        return true;
    }
    /**
     * 過濾目錄下的文件
     * @param dirPath 想要獲取文件的目錄
     * @return 返回文件list
     */
    public static List<File> getFileList(String dirPath) {
        File[] files = new File(dirPath).listFiles();
        List<File> fileList = new ArrayList<File>();
        for (File file : files) {
            if (isTxtFile(file.getName())) {
                fileList.add(file);
            }
        }
        return fileList;
    }
    /**
     * 判斷是否爲目標文件,目前支持txt xls doc格式
     * @param fileName 文件名稱
     * @return 若是是文件類型知足過濾條件,返回true;不然返回false
     */
    public static boolean isTxtFile(String fileName) {
        if (fileName.lastIndexOf(".txt") > 0) {
            return true;
        }else if (fileName.lastIndexOf(".xls") > 0) {
            return true;
        }else if (fileName.lastIndexOf(".doc") > 0) {
            return true;
        }
        return false;
    }
    /**
     * 讀取txt文件的內容
     * @param file 想要讀取的文件對象
     * @return 返回文件內容
     */
    public static String txt2String(File file){
        String result = "";
        try{
            BufferedReader br = new BufferedReader(new FileReader(file));//構造一個BufferedReader類來讀取文件
            String s = null;
            while((s = br.readLine())!=null){//使用readLine方法,一次讀一行
                result = result + "\n" +s;
            }
            br.close();    
        }catch(Exception e){
            e.printStackTrace();
        }
        return result;
    }
    /**
     * 讀取doc文件內容
     * @param file 想要讀取的文件對象
     * @return 返回文件內容
     */
    public static String doc2String(File file){
        String result = "";
        try{
            FileInputStream fis = new FileInputStream(file);
            //適合office2007以上
            OPCPackage pack = POIXMLDocument.openPackage(file.getPath());
            XWPFDocument  docx = new XWPFDocument(pack) ;
            List<XWPFParagraph> paras = docx.getParagraphs(); 
            for (XWPFParagraph para : paras) { 
            result += para.getText().trim();
            } 
            //只適合office2003
//            HWPFDocument doc = new HWPFDocument(fis);
//            Range rang = doc.getRange();
//            result += rang.text();
//            fis.close();
        }catch(Exception e){
            e.printStackTrace();
        }
        return result;
    }
    /**
     * 讀取xls文件內容
     * @param file 想要讀取的文件對象
     * @return 返回文件內容
     */
    public static String xls2String(File file){
        String result = "";
        try{
            FileInputStream fis = new FileInputStream(file);   
            StringBuilder sb = new StringBuilder();   
            jxl.Workbook rwb = Workbook.getWorkbook(fis);   
            Sheet[] sheet = rwb.getSheets();   
            for (int i = 0; i < sheet.length; i++) {   
                Sheet rs = rwb.getSheet(i);   
                for (int j = 0; j < rs.getRows(); j++) {   
                   Cell[] cells = rs.getRow(j);   
                   for(int k=0;k<cells.length;k++)   
                   sb.append(cells[k].getContents());   
                }   
            }   
            fis.close();   
            result += sb.toString();
        }catch(Exception e){
            e.printStackTrace();
        }
        return result;
    }
    public static void closeWriter() throws Exception {
        if (indexWriter != null) {
            indexWriter.close();
        }
    }
    
    /**
     * 建立索引管理器
     * @return 返回索引管理器對象
     */
    public LuceneTest getManager(){
        if(indexManager == null){
            this.indexManager = new LuceneTest();
        }
        return indexManager;
    }
    
}
 要上傳jar包以下圖:

 

6、 內存索引庫和文件索引庫結合

在lucene索引庫的建立的時候,咱們有兩種不一樣的索引庫建立方式
 

1.文件索引庫

final Path docDir = Paths. get ( "index" );
Directory directory=FSDirectory.open(Paths.get("index"));
 
  1.  
    這樣建立的索引庫是在本地磁盤上建立一個 index文件夾,而且將索引放在 index中,也稱爲文件索引庫
  2.  
    優勢:將索引持久化到磁盤上,能長久保存。
  3.  
    缺點:相比較內存索引庫,讀取慢

2.內存索引

Directory directory = new RAMDirectory();
 
  1.  
    只須要一句代碼,就建立了一個內存索引庫
  2.  
    優勢:讀取快
  3.  
    缺點:不具有持久化能力,結束時候內存索引庫便會刪除

3.兩種索引庫的結合

根據兩種索引庫的特色咱們能夠將兩種索引庫結合起來,設計的思路是在程序啓動時,將文件索引庫中的索引拷貝到內存索引庫中,而後讓程序與內存索引庫交互,當交互完畢後再將內存索引庫的索引持久化到文件索引庫。
 
/**
         * 1.建立兩個索引庫
         * 2.建立兩個IndexWriter
         * 3.把文件索引庫中的內容放到內存索引庫中
         * 4.讓內存索引庫和客戶端進行交互
         * 5.把內存索引庫的內容放到文件索引庫
         */
        final Path docDir = Paths.get("index");
        //建立文件索引庫
        Directory fileDirectory=FSDirectory.open(Paths.get("index"));
        //建立內存索引庫
        Directory ramDirectory = new RAMDirectory(FSDirectory.open(Paths.get("index")), null);


        Analyzer analyzer = new StandardAnalyzer();
        IndexWriterConfig iwc = new IndexWriterConfig(analyzer);
        //操做文件的IndexWriter
        IndexWriter fileIndexWriter = new IndexWriter(fileDirectory, iwc);
        //操做內存的IndexWriter
        Analyzer analyzer1 = new StandardAnalyzer();
        IndexWriterConfig iwc1 = new IndexWriterConfig(analyzer1);
        IndexWriter ramIndexWriter=new IndexWriter(ramDirectory, iwc1);


        Article article = new Article();
        article.setAid(1L);
        article.setTitle("lucene是一個全文檢索引擎");
        article.setContent("baidu,google都是很好的全文檢索引擎");


        // 建立document
        Document document = new Document();
        Field idField = new Field("aid", article.getAid().toString(),
                TextField.TYPE_STORED);
        Field titleField = new Field("title", article.getTitle().toString(),
                TextField.TYPE_STORED);
        Field contentField = new Field("content", article.getContent()
                .toString(), TextField.TYPE_STORED);
        document.add(idField);
        document.add(titleField);
        document.add(contentField);

        //把document放到內存當中        ramIndexWriter.addDocument(document);        ramIndexWriter.close();        //把內存索引庫的內容合併到文件索引庫        fileIndexWriter.addIndexes(ramDirectory);         fileIndexWriter.close();
相關文章
相關標籤/搜索