也是項(xiàng)目需要用的框架之一,為了不讓自己輕易忘記它,在此記錄一系列的lucene學(xué)習(xí)筆記(基于lucene4.4,IKAnalyzer2012分詞器,只有4.0之前的api才大變,4.0后的api趨于穩(wěn)定),寫完基礎(chǔ)篇后,我們就進(jìn)行結(jié)合之前的項(xiàng)目寫一系列的實(shí)戰(zhàn)、優(yōu)化和原理的文章。
本系列:
(1)SSM框架構(gòu)建積分系統(tǒng)和基本商品檢索系統(tǒng)(Spring+SpringMVC+MyBatis+Lucene+Redis+MAVEN)(1)框架整合構(gòu)建
(2)SSM框架構(gòu)建積分系統(tǒng)和基本商品檢索系統(tǒng)(Spring+SpringMVC+MyBatis+Lucene+Redis+MAVEN)(2)建立商品數(shù)據(jù)庫(kù)和Lucene的搭建
(3)Redis系列(一)--安裝、helloworld以及讀懂配置文件
(4)Redis系列(二)--緩存設(shè)計(jì)(整表緩存以及排行榜緩存方案實(shí)現(xiàn))
文章結(jié)構(gòu):(1)認(rèn)識(shí)Lucene;(2)Lucene常用類;(3)Lucene的索引文件目錄解析;(4)lucene的helloworld;(5)lucene常用分詞器;(6)lucene多條件查詢;(7)lucene排序;(8)lucene高亮顯示;(9)lucene分頁(yè)
一、認(rèn)識(shí)Lucene:
Lucene是一套用于全文檢索和搜尋的開源程式庫(kù)是全文檢索的框架。lucene其實(shí)就做兩種工作:一入一出。所謂入是寫入,即將你提供的源(本質(zhì)是字符串)寫入索引或者將其從索引中刪除;所謂出是讀出,即向用戶提供全文搜索服務(wù),讓用戶可以通過(guò)關(guān)鍵詞定位源。
它不是一個(gè)完整的全文檢索引擎,而是一個(gè)全文檢索引擎的架構(gòu),提供了完整的查詢引擎和索引引擎,部分文本分析引擎。
生活中的數(shù)據(jù)總體分為兩種:結(jié)構(gòu)化數(shù)據(jù)和非結(jié)構(gòu)化數(shù)據(jù)。非結(jié)構(gòu)化數(shù)據(jù)又一種叫法叫全文數(shù)據(jù):
結(jié)構(gòu)化數(shù)據(jù):指具有固定格式或有限長(zhǎng)度的數(shù)據(jù),如數(shù)據(jù)庫(kù),元數(shù)據(jù)等。
非結(jié)構(gòu)化數(shù)據(jù):指不定長(zhǎng)或無(wú)固定格式的數(shù)據(jù),如郵件,word文檔等。
半結(jié)構(gòu)化數(shù)據(jù),如XML,HTML等,當(dāng)根據(jù)需要可按結(jié)構(gòu)化數(shù)據(jù)來(lái)處理,也可抽取出純文本按非結(jié)構(gòu)化數(shù)據(jù)來(lái)處理。
按照數(shù)據(jù)的分類,搜索也分為兩種:
對(duì)結(jié)構(gòu)化數(shù)據(jù)的搜索:如對(duì)數(shù)據(jù)庫(kù)的搜索,用SQL語(yǔ)句。再如對(duì)元數(shù)據(jù)的搜索,如利用windows搜索對(duì)文件名,類型,修改時(shí)間進(jìn)行搜索等。
對(duì)非結(jié)構(gòu)化數(shù)據(jù)的搜索:如利用windows的搜索也可以搜索文件內(nèi)容,Linux下的grep命令,再如用Google和百度可以搜索大量?jī)?nèi)容數(shù)據(jù)。
對(duì)非結(jié)構(gòu)化數(shù)據(jù)也即對(duì)全文數(shù)據(jù)的搜索主要有兩種方法:
(1)順序掃描法;
比如要找內(nèi)容包含某一個(gè)字符串的文件,就是一個(gè)文檔一個(gè)文檔的看,對(duì)于每一個(gè)文檔,從頭看到尾,如果此文檔包含此字符串,則此文檔為我們要找的文件,接著看下一個(gè)文件,直到掃描完所有的文件。如利用windows的搜索也可以搜索文件內(nèi)容,只是相當(dāng)?shù)穆?。如果你有一個(gè)80G硬盤,如果想在上面找到一個(gè)內(nèi)容包含某字符串的文件,不花他幾個(gè)小時(shí),怕是做不到。Linux下的grep命令也是這一種方式。大家可能覺得這種方法比較原始,但對(duì)于小數(shù)據(jù)量的文件,這種方法還是最直接,最方便的。但是對(duì)于大量的文件,這種方法就很慢了。
(2)全文檢索(先建立索引,再對(duì)索引進(jìn)行搜索的過(guò)程)
比如字典,字典的拼音表和部首檢字表就相當(dāng)于字典的索引,對(duì)每一個(gè)字的解釋是非結(jié)構(gòu)化的,如果字典沒(méi)有音節(jié)表和部首檢字表,在茫茫辭海中找一個(gè)字只能順序掃描。然而字的某些信息可以提取出來(lái)進(jìn)行結(jié)構(gòu)化處理,比如讀音,就比較結(jié)構(gòu)化,分聲母和韻母,分別只有幾種可以一一列舉,于是將讀音拿出來(lái)按一定的順序排列,每一項(xiàng)讀音都指向此字的詳細(xì)解釋的頁(yè)數(shù)。我們搜索時(shí)按結(jié)構(gòu)化的拼音搜到讀音,然后按其指向的頁(yè)數(shù),便可找到我們的非結(jié)構(gòu)化數(shù)據(jù)——也即對(duì)字的解釋。
全文檢索大體分兩個(gè)過(guò)程,索引創(chuàng)建(Indexing)和搜索索引(Search)。
索引創(chuàng)建:將現(xiàn)實(shí)世界中所有的結(jié)構(gòu)化和非結(jié)構(gòu)化數(shù)據(jù)提取信息,創(chuàng)建索引的過(guò)程。
搜索索引:就是得到用戶的查詢請(qǐng)求,搜索創(chuàng)建的索引,然后返回結(jié)果的過(guò)程。
下圖!!首先呈現(xiàn)給用戶的就是這種效果,標(biāo)紅字段,標(biāo)題,概述都是lucene框架所負(fù)責(zé)的(當(dāng)然百度有更牛的檢索框架)。
這里寫圖片描述
二、Lucene常用包和類----官方文檔
包介紹:
1.Lucene-core.jar,核心包,包括了常用的文檔,索引,搜索,存儲(chǔ)等相關(guān)核心代碼。
2.Lucene-analyzers-common.jar,這里面包含了各種語(yǔ)言的詞法分析器,用于對(duì)文件內(nèi)容進(jìn)行關(guān)鍵字切分,提取。
3.Lucene-highlighter.jar,這個(gè)jar包主要用于搜索出的內(nèi)容高亮顯示。
4.Lucene-queryparser.jar,提供了搜索相關(guān)的代碼,用于各種搜索,比如模糊搜索,范圍搜索,等等。
本系列使用了個(gè)中文分詞工具包IKAnalyzer。
類介紹:
描述文字部分參考:這位博主這篇博客
(1)IndexWriter 。索引過(guò)程的核心組件。它屬于索引創(chuàng)建過(guò)程的核心。
增刪改索引都是通過(guò)indexWriter對(duì)象完成。負(fù)責(zé)創(chuàng)建新索引或者打開已有索引,以及向索引中添加、刪除或更新索引文檔的信息。可以把IndexWriter看做這樣一個(gè)對(duì)象:提供針對(duì)索引文件的寫入操作,但不能用于讀取或搜索索引。IndexWriter需要開辟一定空間來(lái)存儲(chǔ)索引,該功能可以由Directory完成。
IndexWriter 的構(gòu)造函數(shù)需要三個(gè)參數(shù),第一個(gè)參數(shù)指定了所創(chuàng)建的索引要存放的位置,他可以是一個(gè) File 對(duì)象,也可以是一個(gè) FSDirectory 對(duì)象或者 RAMDirectory 對(duì)象。后兩個(gè)參數(shù)配置給IndexWriterConfig,然后一起交給indexwriter。第二個(gè)參數(shù)指定了 Analyzer 類的一個(gè)實(shí)現(xiàn),也就是指定這個(gè)索引是用哪個(gè)分詞器對(duì)文擋內(nèi)容進(jìn)行分詞。第三個(gè)參數(shù)是一個(gè)布爾型的變量,如果為 true 的話就代表創(chuàng)建一個(gè)新的索引,為 false 的話就代表在原來(lái)索引的基礎(chǔ)上進(jìn)行操作。接著程序遍歷了目錄下面的所有文本文檔,并為每一個(gè)文本文檔創(chuàng)建了一個(gè) Document 對(duì)象。然后把文本文檔的兩個(gè)屬性:路徑和內(nèi)容加入到了兩個(gè) Field 對(duì)象中,接著在把這兩個(gè) Field 對(duì)象加入到 Document 對(duì)象中,最后把這個(gè)文檔用 IndexWriter 類的 add 方法加入到索引中去。這樣我們便完成了索引的創(chuàng)建。接下來(lái)我們進(jìn)入在建立好的索引上進(jìn)行搜索的部分。
(2)Diretory。索引存放的位置。
它是一個(gè)抽象類,它的子類負(fù)責(zé)具體制定索引的存儲(chǔ)路徑。lucene提供了兩種索引存放的位置,一種是磁盤,一種是內(nèi)存。一般情況將索引放在磁盤上;相應(yīng)地lucene提供了FSDirectory和RAMDirectory兩個(gè)類。
就是負(fù)責(zé)管理這些索引文件,包括數(shù)據(jù)的讀取和寫入,以及索引文件的添加,刪除和合并。
Lucene的索引體系,支持讀共享,寫?yīng)氄嫉姆绞絹?lái)訪問(wèn)索引目錄,也就是它允許多個(gè)線程實(shí)例同時(shí)并發(fā)的讀取,而不允許多個(gè)線程同時(shí)寫入。這種實(shí)現(xiàn)的關(guān)鍵就在Diretory鎖機(jī)制。
還有關(guān)注:FSDirectory 對(duì)象或者 RAMDirectory 對(duì)象
(3)Analyzer
分析器,主要用于分析搜索引擎遇到的各種文本,Analyzer的工作是一個(gè)復(fù)雜的過(guò)程:把一個(gè)字符串按某種規(guī)則劃分成一個(gè)個(gè)詞語(yǔ),并去除其中的無(wú)效詞語(yǔ)(停用詞),這里說(shuō)的無(wú)效詞語(yǔ)如英文中的“of”、“the”,中文中的“的”、“地”等詞語(yǔ),這些詞語(yǔ)在文章中大量出現(xiàn),但是本身不包含什么關(guān)鍵信息,去掉有利于縮小索引文件、提高效率、提高命中率。分詞的規(guī)則千變?nèi)f化,但目的只有一個(gè):按語(yǔ)義劃分。這點(diǎn)在英文中比較容易實(shí)現(xiàn),因?yàn)橛⑽谋旧砭褪且詥卧~為單位的,已經(jīng)用空格分開;而中文則必須以某種方法將連成一片的句子劃分成一個(gè)個(gè)詞語(yǔ)。
(4)Document
相當(dāng)于一個(gè)要進(jìn)行索引的單元,可以是文本文件、字符串或者數(shù)據(jù)庫(kù)表的一條記錄等等,一條記錄經(jīng)過(guò)索引之后,就是以一個(gè)Document的形式存儲(chǔ)在索引文件,索引的文件都必須轉(zhuǎn)化為Document對(duì)象才能進(jìn)行索引。
因?yàn)閘ucene的“索引庫(kù)”中是存放Document的。Document中存放的是該Document的field ,而我們要用Java的面向?qū)ο缶幊蹋荒苤苯影裫ava的對(duì)象直接存放在Lucene的索引庫(kù)中,所以需要轉(zhuǎn)換下。
lucene的“索引庫(kù)”你可以抽象成是數(shù)據(jù)庫(kù),Document你可以抽象成是表。那么field可以抽象成表中的各個(gè)字段。
(5)Field
Field類是文檔索引期間很重要的類,控制著被索引的域值
一個(gè)Document可以包含多個(gè)信息域,比如一篇文章可以包含“標(biāo)題”、“正文”等信息域,這些信息域就是通過(guò)Field在Document中存儲(chǔ)的。你可以類比數(shù)據(jù)庫(kù)表里可以有多個(gè)字段來(lái)理解,雖然兩者不能等同,但有助于你理解每個(gè)Field包含3部分信息:域的名稱,域的類型,域的值。
這個(gè)Field還包括一些存儲(chǔ)細(xì)節(jié):
這里寫圖片描述
Indexed,表示是否創(chuàng)建索引
Analyzed,表示是否進(jìn)行分詞處理
omitNorms,表示是否忽略域的標(biāo)準(zhǔn)化。其實(shí)這個(gè)選項(xiàng)有關(guān)域的權(quán)重計(jì)算的,如果你忽略了域的標(biāo)準(zhǔn)化操作,那么在創(chuàng)建索引的時(shí)候就不會(huì)在域里面多開辟一個(gè)字節(jié)的空間來(lái)存儲(chǔ)起加權(quán)編碼值,因?yàn)樗鼤?huì)多開辟一個(gè)字節(jié)的空間,所以會(huì)稍微增加了內(nèi)存占用,如果你的Field都不需要額外的設(shè)置權(quán)重(注:field.setBoot(1.2)通過(guò)這樣來(lái)設(shè)置域的權(quán)重值),那么你就可以設(shè)置忽略域的標(biāo)準(zhǔn)化操作,即可以減小內(nèi)存占用,但它也會(huì)潛在的影響域的評(píng)分。
(6)IndexReader ,打開一個(gè)Directory讀取索引類。
lucene優(yōu)點(diǎn)很重要的一點(diǎn):不要頻繁去打開關(guān)閉硬盤索引。對(duì)于此處,IndexReader的實(shí)例化過(guò)程是一個(gè)非常耗時(shí)的過(guò)程。
(7)IndexSearcher ,lucene中最基本的檢索工具,所有的檢索都會(huì)用到IndexSearcher工具。
1. IndexSearcher提供了對(duì)單個(gè)IndexReader的查詢實(shí)現(xiàn);
2. 通過(guò)調(diào)用search(Query,n)或者search(Query,Filter,n)方法;
3. 在索引內(nèi)容變動(dòng)不大的情況下,我們可以對(duì)索引的搜索采用單個(gè)IndexSearcher共享的方式來(lái)提升性能;
4.如果索引有變動(dòng),我們就需要使用DirectoryReader.openIfChanged(DirectoryReader)來(lái)獲取新的reader,然后創(chuàng)建新的IndexSearcher對(duì)象;
5.為了使查詢延遲率低,我們最好使用近實(shí)時(shí)搜索的方法(此時(shí)我們的DirectoryReader的構(gòu)建就要采用DirectoryReader.open(IndexWriter, boolean))
6.IndexSearcher實(shí)例是完全線程安全的,這意味著多個(gè)線程可以并發(fā)調(diào)用任何方法。如果需要外部同步,無(wú)需添加IndexSearcher的同步;
7.如果我們想提高IndexSearcher的執(zhí)行效率可以new IndexSearcher(DirecotoryReader,ExcuterService)來(lái)創(chuàng)建IndexSearcher對(duì)象,這樣做的好處為對(duì)每塊segment采用了分工查詢,但是要注意IndexSearcher并不維護(hù)ExcuterService的生命周期,我們還需要自行調(diào)用ExcuterService的close/awaitTermination
(8)Query ,查詢,抽象類,必須通過(guò)一系列子類來(lái)表述檢索的具體需求。
lucene中支持模糊查詢,語(yǔ)義查詢,短語(yǔ)查詢,組合查詢等等,如有 TermQuery,BooleanQuery,RangeQuery,WildcardQuery等一些類。
1. TermQuery。搜索索引最基本的查詢類型??梢允褂脝蝹€(gè)項(xiàng)構(gòu)建 TermQuery。項(xiàng)值應(yīng)該區(qū)分大小寫,但也并非全是如此。注意,傳遞的搜索項(xiàng)應(yīng)該與文檔分析得到的項(xiàng)一致,因?yàn)榉治龀绦蛟跇?gòu)建索引之前對(duì)原文本執(zhí)行許多操作。
例如,考慮電子郵件標(biāo)題 “Job openings for Java Professionals at Bangalore”。假設(shè)您使用 StandardAnalyzer 編制索引?,F(xiàn)在如果我們使用 TermQuery 搜索 “Java”,它不會(huì)返回任何內(nèi)容,因?yàn)楸疚谋緫?yīng)該已經(jīng)規(guī)范化,并通過(guò) StandardAnalyzer 轉(zhuǎn)成小寫。如果搜索小寫單詞 “java”,它將返回所有標(biāo)題字段中包含該單詞的郵
2. RangeQuery范圍搜索。索引中的所有項(xiàng)都以字典順序排列。Lucene 的 RangeQuery 允許用戶在某個(gè)范圍內(nèi)搜索項(xiàng)。該范圍可以使用起始項(xiàng)和最終項(xiàng)(包含兩端或不包含兩端均可)指定。
3.PrefixQuery前綴搜索。通過(guò)前綴單詞進(jìn)行搜索,該方法用于構(gòu)建一個(gè)查詢,該查詢查找包含以指定單詞前綴開始的詞匯的文檔。
4.BooleanQuery布爾搜索??梢越M合任何數(shù)量的查詢對(duì)象,構(gòu)建強(qiáng)大的查詢。它使用 query 和一個(gè)關(guān)聯(lián)查詢的子句,指示查詢是應(yīng)該發(fā)生、必須發(fā)生還是不得發(fā)生。在 BooleanQuery 中,子句的最大數(shù)量默認(rèn)限制為 1,024。您可以調(diào)用 setMaxClauseCount 方法設(shè)置最大子句數(shù)。
5.WildcardQuery通配符搜索。實(shí)現(xiàn)通配符搜索查詢,這允許您搜索 arch(可以查找包含 architect、architecture 等)之類的單詞。使用兩個(gè)標(biāo)準(zhǔn)通配符: 表示零個(gè)以上;? 表示一個(gè)以上。如果使用以通配符查詢開始的模式進(jìn)行搜索,則可能會(huì)引起性能的降低,因?yàn)檫@需要查詢索引中的所有項(xiàng)以查找匹配文檔。
(9)QueryParser。解析用戶的查詢字符串進(jìn)行搜索,是一個(gè)解析用戶輸入的工具,可以通過(guò)掃描用戶輸入的字符串,生成Query對(duì)象。
對(duì)于解析人工輸入的查詢字符非常有用。您可以使用它將用戶輸入的查詢表達(dá)式解析為 Lucene 查詢對(duì)象,這些對(duì)象可以傳遞到 IndexSearcher 的搜索方法。它可以解析豐富的查詢表達(dá)式。 QueryParser 內(nèi)部將人們輸入的查詢字符串轉(zhuǎn)換為一個(gè)具體的查詢子類。您需要使用反斜杠(\)將 *、? 等特殊字符進(jìn)行轉(zhuǎn)義。您可以使用運(yùn)算符 AND、OR 和 NOT 構(gòu)建文本布爾值查詢。
(10)TopDocs 。根據(jù)關(guān)鍵字搜索整個(gè)索引庫(kù),然后對(duì)所有結(jié)果進(jìn)行排序,取指定條目的結(jié)果。這就是一個(gè)返回的結(jié)果集,是包含結(jié)果的多個(gè)信息的一個(gè)對(duì)象。其中有totalHits代表記錄數(shù),ScoreDoc的數(shù)組。。
(11)TokenStream.是一個(gè)由分詞后的Token結(jié)果組成的流,能夠不斷的得到下一個(gè)分成的Token。
1.NumericTokenStream。提供對(duì)數(shù)字范圍的支持
2.SingleTokenTokenStream。此TokenStream僅僅包含一個(gè)Token,多用于保存一篇文檔僅有一個(gè)的信息,如id,如time等,這些信息往往被保存在一個(gè)特殊的Token(如ID:ID, TIME:TIME)的倒排表的payload中的,這樣可以使用跳表來(lái)增加訪問(wèn)速度。
所以SingleTokenTokenStream返回的Token則不是id或者time本身,而是特殊的Token,"ID:ID", "TIME:TIME",而是將id的值或者time的值放入payload中。
3.還有Tokenizer,CharTokenizer,ChineseTokenizer,KeywordTokenizer等等就不多說(shuō)了,還是各自查文檔吧。
(12)ScoreDoc。是代表一個(gè)結(jié)果的相關(guān)度得分與文檔編號(hào)等信息的對(duì)象。這是一個(gè)數(shù)組,一個(gè)怎樣的數(shù)組??數(shù)組的每一位就是包含了我們索引的值、查詢記錄的評(píng)分、索引中文檔的位置等等。
(3)Term。搜索的基本單元,一個(gè)Term對(duì)象有兩個(gè)String類型的域組成:字段名稱和字段的值。
在搜索的時(shí)候,我們經(jīng)常創(chuàng)建Term和TermQuery同時(shí)使用,其中第一個(gè)參數(shù)代表了要在文檔哪個(gè)Field上進(jìn)行查找,第二個(gè)參數(shù)代表要查詢的關(guān)鍵詞。
三、Lucene的索引文件目錄解析:
這里寫圖片描述
這里寫圖片描述
(1)Lucene的索引結(jié)構(gòu)是有層次結(jié)構(gòu)的:
1.索引(Index):
在Lucene中一個(gè)索引是放在一個(gè)文件夾中的。如上圖,同一文件夾中的所有的文件構(gòu)成一個(gè)Lucene索引。
2.段(Segment):
一個(gè)索引可以包含多個(gè)段,段與段之間是獨(dú)立的,添加新文檔可以生成新的段,不同的段可以合并。
如上圖,具有相同前綴文件的屬同一個(gè)段,圖中共兩個(gè)段 "_0" 和 "_1"。
segments.gen和segments_5是段的元數(shù)據(jù)文件,也即它們保存了段的屬性信息。
3.文檔(Document):
文檔是我們建索引的基本單位,不同的文檔是保存在不同的段中的,一個(gè)段可以包含多篇文檔。
新添加的文檔是單獨(dú)保存在一個(gè)新生成的段中,隨著段的合并,不同的文檔合并到同一個(gè)段中。
4.域(Field):
一篇文檔包含不同類型的信息,可以分開索引,比如標(biāo)題,時(shí)間,正文,作者等,都可以保存在不同的域里。
不同域的索引方式可以不同.
5.詞(Term):
詞是索引的最小單位,是經(jīng)過(guò)詞法分析和語(yǔ)言處理后的字符串。
(2)Lucene的索引結(jié)構(gòu)中,即保存了正向信息,也保存了反向信息。
[一] 正向信息:
按層次保存了從索引,一直到詞的包含關(guān)系:索引(Index) –> 段(segment) –> 文檔(Document) –> 域(Field) –> 詞(Term)
也就是索引包含了那些段,每個(gè)段包含了那些文檔,每個(gè)文檔包含了那些域,每個(gè)域包含了那些詞。
既然是層次結(jié)構(gòu),則每個(gè)層次都保存了本層次的信息以及下一層次的元信息,也即屬性信息,比如一本介紹中國(guó)地理的書,應(yīng)該首先介紹中國(guó)地理的概況,以及中國(guó)包含多少個(gè)省,每個(gè)省介紹本省的基本概況及包含多少個(gè)市,每個(gè)市介紹本市的基本概況及包含多少個(gè)縣,每個(gè)縣具體介紹每個(gè)縣的具體情況。
如上圖,包含正向信息的文件有:
segments_N保存了此索引包含多少個(gè)段,每個(gè)段包含多少篇文檔。
XXX.fnm保存了此段包含了多少個(gè)域,每個(gè)域的名稱及索引方式。
XXX.fdx,XXX.fdt保存了此段包含的所有文檔,每篇文檔包含了多少域,每個(gè)域保存了那些信息。.fdt用于存儲(chǔ)具有Store.YES屬性的Field的數(shù)據(jù);.fdt則是一個(gè)索引,用于存儲(chǔ)Document在.fdt中的位置。
XXX.tvx,XXX.tvd,XXX.tvf保存了此段包含多少文檔,每篇文檔包含了多少域,每個(gè)域包含了多少詞,每個(gè)詞的字符串,位置等信息。
XXX.cfs復(fù)合索引格式。在IndexWriter總有一個(gè)屬性:useCompoundFile, 它的默認(rèn)值為true, 這個(gè)屬性的含義為是否使用復(fù)合索引格式來(lái)保存索引。索引的內(nèi)容可能會(huì)非常大文件數(shù)目非常大將極消耗系統(tǒng)資源。因此Lucene提供了一種單文本索引格式,也就是所謂的復(fù)合索引格式。使用復(fù)合索引格式存儲(chǔ)Document內(nèi)容時(shí),只需要在初始化完一個(gè)IndexWriter對(duì)象后,使用setUseCompoundFile(boolean)方法。將useCompoundFile的屬性值設(shè)置為true就可以了。
[二]反向信息:
保存了詞典到倒排表的映射:詞(Term) –> 文檔(Document)
如上圖,包含反向信息的文件有:
XXX.tis,XXX.tii保存了詞典(Term Dictionary),也即此段包含的所有的詞按字典順序的排序。.tis用于存儲(chǔ)分詞后的詞條(Term), 而.tii就是它的索引文件,它標(biāo)明了每個(gè).tis文件中的詞條位置。。
XXX.frq保存了倒排表,也即包含每個(gè)詞的文檔ID列表。
XXX.prx保存了倒排表中每個(gè)詞在包含此詞的文檔中的位置。
四、lucene的helloworld:
Lucene的搭建
五、lucene常用分詞器:
展示四個(gè)分詞器用法:
//分詞器:
@Test
public void analyzer() throws IOException {
String txt = "我在學(xué)習(xí)java";
// Analyzer analyzer1 = new StandardAnalyzer(Version.LUCENE_44);// 標(biāo)準(zhǔn)分詞器
// Analyzer analyzer2 = new SimpleAnalyzer(Version.LUCENE_44);// 簡(jiǎn)單分詞器
// Analyzer analyzer3 = new CJKAnalyzer(Version.LUCENE_44);// 二元切分
Analyzer analyzer4 = new IKAnalyzer(false);// 語(yǔ)意分詞
// TokenStream tokenstream = analyzer1.tokenStream("content", new StringReader(txt));// 生成一個(gè)分詞流
// TokenStream tokenstream = analyzer2.tokenStream("content", new StringReader(txt));
// TokenStream tokenstream = analyzer3.tokenStream("content", new StringReader(txt));
TokenStream tokenstream = analyzer4.tokenStream("content", new StringReader(txt));
CharTermAttribute termAttribute = tokenstream.addAttribute(CharTermAttribute.class);// 為token設(shè)置屬性類
tokenstream.reset();// 重新設(shè)置
while (tokenstream.incrementToken()) {// 遍歷得到token
System.out.print(new String(termAttribute.buffer(), 0, termAttribute.length()) + " ");
}
}
/*
運(yùn)行結(jié)果:
1.我 在 學(xué) 習(xí) java
2.我在學(xué)習(xí)java
3.我在 在學(xué) 學(xué)習(xí) java
4. 在學(xué) 學(xué)習(xí) java
*/
IKAnalyzer更進(jìn)一步:根據(jù)詞典去分詞,為了讓更精準(zhǔn)分詞,還有個(gè)去除標(biāo)點(diǎn)符號(hào)的功能。比如:我在學(xué)-*習(xí)java,IKAnalyzer分詞結(jié)果:學(xué)習(xí) java
加載擴(kuò)展詞典:mydict.dic
加載擴(kuò)展停止詞典:ext_stopword.dic
在學(xué) 學(xué)習(xí) java
我們使用的那兩個(gè)文件就去下面源碼下載看吧,有點(diǎn)大。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 擴(kuò)展配置</comment>
<!--用戶可以在這里配置自己的擴(kuò)展字典,多個(gè)字典以分號(hào);隔開 -->
<entry key="ext_dict">mydict.dic;</entry>
<!--用戶可以在這里配置自己的擴(kuò)展停用詞字典 -->
<entry key="ext_stopwords">ext_stopword.dic</entry>
</properties>
六、lucene多條件查詢: 學(xué)自此文章
多條件查詢 查詢內(nèi)容必須包含life內(nèi)容和評(píng)分大于等于60分的結(jié)果
public void query() throws IOException, ParseException {
IndexReader reader = DirectoryReader.open(FSDirectory.open(new File("F:/lucene/index01")));// 索引讀取類
IndexSearcher search = new IndexSearcher(reader);// 搜索入口工具類
String queryStr1 = "java";// 搜索關(guān)鍵字
BooleanQuery booleanQuery = new BooleanQuery();
// 條件一內(nèi)容中必須要有l(wèi)ife內(nèi)容
QueryParser queryParser = new QueryParser(Version.LUCENE_44, "content", new StandardAnalyzer(Version.LUCENE_44));// 實(shí)例查詢條件類
Query query1 = queryParser.parse(queryStr1);
// 條件二評(píng)分大于等于60
Query query2 = NumericRangeQuery.newIntRange("score", 60, null, true, false);
booleanQuery.add(query1, BooleanClause.Occur.MUST);
booleanQuery.add(query2, BooleanClause.Occur.MUST);
TopDocs topdocs = search.search(booleanQuery, 100);// 查詢前100條
System.out.println("查詢結(jié)果總數(shù)---" + topdocs.totalHits);
ScoreDoc scores[] = topdocs.scoreDocs;// 得到所有結(jié)果集
for (int i = 0; i < scores.length; i++) {
int num = scores[i].doc;// 得到文檔id
Document document = search.doc(num);// 拿到指定的文檔
System.out.println("內(nèi)容====" + document.get("content"));// 由于內(nèi)容沒(méi)有存儲(chǔ)所以執(zhí)行結(jié)果為null
System.out.println("id--" + num + "---scors--" + scores[i].score + "---index--" + scores[i].shardIndex);
}
}
NumericRangeQuery創(chuàng)建一個(gè)查詢條件。五個(gè)參數(shù)分別為字段域、最小值、最大值、是否包含最小值、是否包含最大值。單范圍查詢,后來(lái)看api才發(fā)現(xiàn)單范圍時(shí)可以用null或者把是否包含范圍值設(shè)為false就行了。
BooleanClause用于表示布爾查詢子句關(guān)系的類,包括:BooleanClause.Occur.MUST,BooleanClause.Occur.MUST_NOT,BooleanClause.Occur.SHOULD。有以下6種組合:
1. MUST和MUST:取得連個(gè)查詢子句的交集。
2. MUST和MUST_NOT:表示查詢結(jié)果中不能包含MUST_NOT所對(duì)應(yīng)得查詢子句的檢索結(jié)果。
3.MUST_NOT和MUST_NOT:無(wú)意義,檢索無(wú)結(jié)果。
4.SHOULD與MUST、SHOULD與MUST_NOT:SHOULD與MUST連用時(shí),無(wú)意義,結(jié)果為MUST子句的檢索結(jié)果。與MUST_NOT連用時(shí),功能同MUST。
5.SHOULD與SHOULD:表示“或”關(guān)系,最終檢索結(jié)果為所有檢索子句的并集。
七、lucene排序:
lucene有自帶的給搜索的每個(gè)document進(jìn)行評(píng)分,并且接收排序要求喔?。?!
@Test
public void defaultSortTest() throws IOException, ParseException {
IndexReader reader = DirectoryReader.open(FSDirectory.open(new File("F:/lucene/index01")));// 索引讀取類
IndexSearcher search = new IndexSearcher(reader);// 搜索入口工具類
String queryStr = "Stack";// 搜索關(guān)鍵字
QueryParser queryParser = new QueryParser(Version.LUCENE_44, "content", new StandardAnalyzer(Version.LUCENE_44));// 實(shí)例查詢條件類
Query query = queryParser.parse(queryStr);
TopDocs topdocs = search.search(query, 100);// 查詢前100條
System.out.println("查詢結(jié)果總數(shù)---" + topdocs.totalHits);
ScoreDoc scores[] = topdocs.scoreDocs;// 得到所有結(jié)果集
for (int i = 0; i < scores.length; i++) {
int num = scores[i].doc;// 得到文檔id
Document document = search.doc(num);// 拿到指定的文檔
System.out.println("內(nèi)容====" + document.get("content"));// 由于內(nèi)容沒(méi)有存儲(chǔ)所以執(zhí)行結(jié)果為null
System.out.println("id--" + num + "---scors--" + scores[i].score + "---index--" + scores[i].shardIndex);
}
}
/*
結(jié)果:
查詢結(jié)果總數(shù)---4
內(nèi)容====null
id--2---scors--0.18100528---index---1
內(nèi)容====null
id--5---scors--0.18100528---index---1
內(nèi)容====null
id--0---scors--0.09599255---index---1
內(nèi)容====null
id--3---scors--0.09599255---index---1
*/
search方法,是可以接收排序要求的。
INDEXORDER通過(guò)Doc的id進(jìn)行排序;Sort.RELEVANCE是從積分高到低排序查出
八、lucene高亮:
高亮顯示,就是根據(jù)用戶輸入的檢索關(guān)鍵字,檢索找到該關(guān)鍵字對(duì)應(yīng)的檢索結(jié)果文件,提取對(duì)應(yīng)于該文件的摘要文本,然后根據(jù)設(shè)置的高亮格式,將格式寫入到摘要文本中對(duì)應(yīng)的與關(guān)鍵字相同或相似的詞條上,在網(wǎng)頁(yè)上顯示出來(lái),該摘要中的與關(guān)鍵字有關(guān)的文本就會(huì)以高亮的格式顯示出來(lái)。
高亮顯示需要幾個(gè)實(shí)現(xiàn)類:
(1)Fragmenter接口。作用是將原始字符串拆分成獨(dú)立的片段。有三個(gè)實(shí)現(xiàn)類: NullFragmenter 是該接口的一個(gè)具體實(shí)現(xiàn)類,它將整個(gè)字符串作為單個(gè)片段返回,這適合于處理title域和前臺(tái)文本較短的域,而對(duì)于這些域來(lái)說(shuō),我們是希望在搜索結(jié)果中全部展示。SimpleFragmenter 是負(fù)責(zé)將文本拆分封固定字符長(zhǎng)度的片段,但它并處理子邊界。你可以指定每個(gè)片段的字符長(zhǎng)度(默認(rèn)情況100)但這類片段有點(diǎn)過(guò)于簡(jiǎn)單,在創(chuàng)建片段時(shí),他并不限制查詢語(yǔ)句的位置,因此對(duì)于跨度的匹配操作會(huì)輕易被拆分到兩個(gè)片段中; SimpleSpanFragmenter 是嘗試將讓片段永遠(yuǎn)包含跨度匹配的文檔。
(2)(2)Scorer接口。Fragmenter輸出的是文本片段序列,而Highlighter必須從中挑選出最適合的一個(gè)或多個(gè)片段呈現(xiàn)給客戶,為了做到這點(diǎn),Highlighter會(huì)要求Scorer來(lái)對(duì)每個(gè)片段進(jìn)行評(píng)分。有兩個(gè)實(shí)現(xiàn)類:QueryTermScorer 基于片段中對(duì)應(yīng)Query的項(xiàng)數(shù)進(jìn)行評(píng)分。QueryScorer只對(duì)促成文檔匹配的實(shí)際項(xiàng)進(jìn)行評(píng)分。
(3)Formatter接口。它負(fù)責(zé)將片段轉(zhuǎn)換成String形式,以及將被高亮顯示的項(xiàng)一起用于搜索結(jié)果展示以及高亮顯示。有兩個(gè)類:SimpleHTMLFormatter簡(jiǎn)單的html格式。GradientFormatter復(fù)雜型式對(duì)不同的得分實(shí)現(xiàn)不同的樣式。
@Test
public void highlighter() throws IOException, ParseException, InvalidTokenOffsetsException {
IndexReader reader = DirectoryReader.open(FSDirectory.open(new File("F:/lucene/index01")));// 索引讀取類
IndexSearcher search = new IndexSearcher(reader);// 搜索入口工具類
Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_44);// 分詞器
QueryParser qp = new QueryParser(Version.LUCENE_44, "content", analyzer);// 實(shí)例查詢條件類
Query query = qp.parse("Java");
TopDocs topDocs = search.search(query, 100);// 查詢前100條
System.out.println("共查詢出:" + topDocs.totalHits + "條數(shù)據(jù)");
ScoreDoc scoreDoc[] = topDocs.scoreDocs;// 結(jié)果集
// 高亮
Formatter formatter = new SimpleHTMLFormatter("<font color='red'>", "</font>");// 高亮html格式
Scorer score = new QueryScorer(query);// 檢索評(píng)份
Fragmenter fragmenter = new SimpleFragmenter(100);// 設(shè)置最大片斷為100
Highlighter highlighter = new Highlighter(formatter, score);// 高亮顯示類
highlighter.setTextFragmenter(fragmenter);// 設(shè)置格式
for (int i = 0; i < scoreDoc.length; i++) {// 遍歷結(jié)果集
int docnum = scoreDoc[i].doc;
Document doc = search.doc(docnum);
String content = doc.get("content");
System.out.println(content);// 原內(nèi)容
if (content != null) {
TokenStream tokenStream = analyzer.tokenStream("content", new StringReader(content));
String str = highlighter.getBestFragment(tokenStream, content);// 得到高亮顯示后的內(nèi)容
System.out.println(str);
}
}
}
下篇文章講介紹怎么利用各個(gè)lucene模塊完成這樣的效果啦?。?!然后再講優(yōu)化。
這里寫圖片描述
九、lucene分頁(yè):
lucene的分頁(yè)有兩種方式:(1)查詢出所有結(jié)果然后進(jìn)行分布。(2)通過(guò)TopScoreDocCollector.topDocs(int num1,int num2)來(lái)實(shí)現(xiàn)分頁(yè)。
@Test
public void pageTest() throws IOException, ParseException {
IndexReader reader = DirectoryReader.open(FSDirectory.open(new File("F:/lucene/index01")));// 索引讀取類
IndexSearcher search = new IndexSearcher(reader);// 搜索入口工具類
String queryStr = "Java";// 搜索關(guān)鍵字
QueryParser queryParser = new QueryParser(Version.LUCENE_44, "content", new StandardAnalyzer(Version.LUCENE_44.LUCENE_44));// 實(shí)例查詢條件類
Query query = queryParser.parse(queryStr);// 查詢
TopScoreDocCollector results = TopScoreDocCollector.create(100, false);// 結(jié)果集
search.search(query, results);// 查詢前100條
TopDocs topdocs = results.topDocs(1, 2);// 從結(jié)果集中第1條開始取2條
ScoreDoc scores[] = topdocs.scoreDocs;// 得到所有結(jié)果集
for (int i = 0; i < scores.length; i++) {
int num = scores[i].doc;// 得到文檔id
Document document = search.doc(num);// 拿到指定的文檔
System.out.println("內(nèi)容====" + document.get("content"));// 由于內(nèi)容沒(méi)有存儲(chǔ)所以執(zhí)行結(jié)果為null
System.out.println("id--" + num + "---scors--" + scores[i].score + "---index--" + scores[i].shardIndex);
}
}