分布式--Lucene 全文檢索

1. Lucene 官網(wǎng)

1). 概述

Lucene是一款高性能的、可擴(kuò)展的信息檢索(IR)工具庫(kù)。信息檢索是指文檔搜索、文檔內(nèi)信息搜索或者文檔相關(guān)的元數(shù)據(jù)搜索等操作。Lucene工具包下載

2). 索引過(guò)程:

①獲取內(nèi)容
②建立文檔
獲取原始內(nèi)容后,就需要對(duì)這些內(nèi)容進(jìn)行索引,必須首先將這些內(nèi)容轉(zhuǎn)換成部件(通常稱為文檔),以供搜索引擎使用。文檔主要包括幾個(gè)帶值的域,比如標(biāo)題、正文、摘要、作者和鏈接。
③文檔分析
搜索引擎不能直接對(duì)文本進(jìn)行索引:確切地說(shuō),必須將文本分割成一系列被稱為語(yǔ)匯單元的獨(dú)立的原子元素。每一個(gè)語(yǔ)匯單元大致與語(yǔ)言中的“單詞”對(duì)應(yīng)起來(lái)。
④文檔索引
在索引步驟中,文檔被加入到索引列表。

3). 搜索組件

搜索處理過(guò)程就是從索引中查找單詞,從而找到包含該單詞的文檔。搜索質(zhì)量主要由查準(zhǔn)率和查全率來(lái)衡量。查全率用來(lái)衡量搜索系統(tǒng)查找相關(guān)文檔的能力;而查準(zhǔn)率用來(lái)衡量搜索系統(tǒng)過(guò)濾非相關(guān)文檔的能力。
①用戶搜索界面
Lucene不提供默認(rèn)的用戶搜索界面,需要自己開(kāi)發(fā)。
②建立查詢
用戶從搜索界面提交一個(gè)搜索請(qǐng)求,通常以HTML表單或者Ajax請(qǐng)求的形式由瀏覽器提交到你的搜索引擎服務(wù)器。然后將這個(gè)請(qǐng)求轉(zhuǎn)換成搜索引擎使用的查詢對(duì)象格式,這稱為建立查詢。
③搜索查詢
查詢檢索索引并返回與查詢語(yǔ)句匹配的文檔,結(jié)果返回時(shí)按照查詢請(qǐng)求來(lái)排序。
④展現(xiàn)結(jié)果
一旦獲得匹配查詢語(yǔ)句并排好序的文檔結(jié)果集,接下來(lái)就得用直觀的、經(jīng)濟(jì)的方式為用戶展現(xiàn)結(jié)果。

4). 索引過(guò)程的核心類
IndexWriter 
Directory 
Analyzer 
Document 
Field

①IndexWriter
索引過(guò)程的核心組件。這個(gè)類負(fù)責(zé)創(chuàng)建新索引或者打開(kāi)已有索引,以及向索引中添加、刪除或更新被索引文檔的信息??梢园袸ndexWriter看作這樣一個(gè)對(duì)象:它為你提供針對(duì)索引文件的寫(xiě)入操作,但不能用于讀取或搜索索引。IndexWriter需要開(kāi)辟一定空間來(lái)存儲(chǔ)索引,該功能可以由Directory完成。
②Directory
該類描述了Lucene索引的存放位置。它是一個(gè)抽象類,它的子類負(fù)責(zé)具體指定索引的存儲(chǔ)路徑。用FSDirectory.open方法來(lái)獲取真實(shí)文件在文件系統(tǒng)的存儲(chǔ)路徑,然后將它們一次傳遞給IndexWriter類構(gòu)造方法。IndexWriter不能直接索引文本,這需要先由Analyzer將文本分割成獨(dú)立的單詞才行。
③Analyzer
文本文件在被索引之前,需要經(jīng)過(guò)Analyzer(分析器)處理。Analyzer是由IndexWriter的構(gòu)造方法來(lái)指定的,它負(fù)責(zé)從被索引文本文件中提取語(yǔ)匯單元,并提出剩下的無(wú)用信息。如果被索引內(nèi)容不是純文本文件,那就需要先將其轉(zhuǎn)換為文本文檔。對(duì)于要將Lucene集成到應(yīng)用程序的開(kāi)發(fā)人員來(lái)說(shuō),選擇什么樣Analyzer是程序設(shè)計(jì)中非常關(guān)鍵的一步。分析器的分析對(duì)象為文檔,該文檔包含一些分離的能被索引的域。
④Document
Document對(duì)象代表一些域(Field)的集合。文檔的域代表文檔或者文檔相關(guān)的一些元數(shù)據(jù)。元數(shù)據(jù)(如作者、標(biāo)題、主題和修改日期等)都作為文檔的不同域單獨(dú)存儲(chǔ)并被索引。Document對(duì)象的結(jié)構(gòu)比較簡(jiǎn)單,為一個(gè)包含多個(gè)Filed對(duì)象容器;Field是指包含能被索引的文本內(nèi)容的類。
⑤Field
索引中的每個(gè)文檔都包含一個(gè)或多個(gè)不同命名的域,這些域包含在Field類中。每個(gè)域都有一個(gè)域名和對(duì)應(yīng)的域值,以及一組選項(xiàng)來(lái)精確控制Lucene索引操作各個(gè)域值。

5).搜索過(guò)程中的核心類
IndexSearcher 
Term 
Query 
TermQuery 
TopDocs

①IndexSearcher
該類用于搜索由IndexWriter類創(chuàng)建的索引,它是連接索引的中心環(huán)節(jié)??梢詫ndexSearcher類看作是一個(gè)以只讀方式打開(kāi)索引的類。它需要利用Directory實(shí)例來(lái)掌控前期創(chuàng)建的索引,然后才能提供大量的搜索方法。
②Term
Term對(duì)象是搜索功能的基本單元。Term對(duì)象包含一對(duì)字符串元素:域名和單詞(或域名文本值)。
③Query
包含了一些非常有用的方法,TermQuery是它的一個(gè)子類。
④TermQuery
該類提供最基本的查詢,用來(lái)匹配指定域中包含特定項(xiàng)的文檔。
⑤TopDocs
該類是一個(gè)簡(jiǎn)單的指針容器,指針一般指向前N個(gè)排名的搜索結(jié)果,搜索結(jié)果即匹配查詢條件的文檔。

6). 域索引選項(xiàng)
  • Index.ANALYZED:使用分析器將值域分解成獨(dú)立的語(yǔ)匯單元流,并使每個(gè)語(yǔ)匯單元能被搜索。該選項(xiàng)使用于普通文本域(如正文、標(biāo)題、摘要等)。
  • Index.NOT_ANALYZED:對(duì)域進(jìn)行索引,但不對(duì)String值進(jìn)行分析。該操作實(shí)際上將值域作為單一語(yǔ)匯單元并使之能被搜索。該選項(xiàng)適用于索引那些不能被分解的值域,如URL、文件路徑、日期、人名、社保號(hào)碼和電話號(hào)碼等。該選項(xiàng)尤其適用于“精確匹配”搜索。
  • Index.ANALYZED_NO_NORMS:不會(huì)在索引中存儲(chǔ)norms信息。
  • Index.NOT_ANALYZED_NO_NORMS:不存儲(chǔ)norms。用于在搜索期間節(jié)省索引空間和減少內(nèi)存耗費(fèi)。
7).域存儲(chǔ)選項(xiàng)
  • Store.YES:指定存儲(chǔ)域值。該情況下,原始的字符串值全部被保存在索引中,并可以由IndexReader類恢復(fù)。該選項(xiàng)對(duì)于需要展示搜索結(jié)果的一些域很有用(如URL、標(biāo)題或數(shù)據(jù)庫(kù)主鍵)。如果索引的大小在搜索程序考慮之列的話,不要存儲(chǔ)太大的域值,因?yàn)榇鎯?chǔ)這些域值會(huì)消耗掉索引的存儲(chǔ)空間。
  • Store.NO:指定不存儲(chǔ)域值。
8). Lucene 并發(fā)處理規(guī)則

任意數(shù)量的制度只讀的IndexReader類都可以同時(shí)打開(kāi)一個(gè)索引。在單個(gè)JVM內(nèi),利用資源和發(fā)揮效率最好的辦法是用多線程共享單個(gè)的IndexReader實(shí)例。
對(duì)于一個(gè)索引來(lái)說(shuō),一次只能打開(kāi)一個(gè)Writer。lucene采用文件鎖來(lái)提供保障。一旦建立起IndexWriter對(duì)象,系統(tǒng)會(huì)分配一個(gè)鎖,該鎖只有當(dāng)IndexWriter對(duì)象被關(guān)閉時(shí)才會(huì)釋放。
IndexReader 對(duì)象甚至可以在IndexWriter對(duì)象正在修改索引時(shí)打開(kāi)。每個(gè)IndexReader對(duì)象將向索引展示自己被打開(kāi)的時(shí)間點(diǎn)。該對(duì)象只有在IndexWriter對(duì)象提交修改或自己被重新打開(kāi)后才能獲知索引的修改情況。在已經(jīng)有IndexReader對(duì)象被打開(kāi)的情況下,打開(kāi)新的IndexReader時(shí)采用參數(shù)cache=true,這樣新的IndexReader會(huì)持續(xù)檢查索引的情況。
任何多個(gè)線程都可以共享同一個(gè)IndexReader類或IndexWriter類。這些類不僅是線程安全的,而且是線程友好的。

9). IndexReader和IndexWriter刪除文檔的區(qū)別
  • IndexReader能夠根據(jù)文檔號(hào)刪除文檔。Indexwriter不能進(jìn)行這樣的操作,因?yàn)槲臋n號(hào)可能因?yàn)槎魏喜⒉僮鞫⒓串a(chǎn)生變化。
  • IndexReader 可以通過(guò)Term對(duì)象刪除文檔,與IndexWriter類似。但I(xiàn)ndexReader會(huì)返回被刪除的文檔號(hào),而IndexWriter不能。IndexReader可以立即決定刪除哪個(gè)文檔,因此就能夠?qū)@些文檔數(shù)量進(jìn)行計(jì)算;而IndexWriter僅僅是將被刪除的Term進(jìn)行緩存,后續(xù)在進(jìn)行實(shí)際的刪除操作。
  • 如果程序使用相同的reader進(jìn)行搜索的話,IndexReader的刪除操作會(huì)即使生效。IndexWriter的刪除操作必須等到程序打開(kāi)一個(gè)新Reader時(shí)才能被感知。
  • IndexWriter可以通過(guò)Query對(duì)象執(zhí)行刪除操作,但I(xiàn)ndexReader則不行。

2. 子類

1). Directory子類
  • SimpleFSDirectory
    最簡(jiǎn)單的Directory子類,使用java.io.* API將文件存入文件系統(tǒng),不能很好的支持多線程
    NIOFSDirectory
  • 使用java.nio.* API 將文件保存至文件系統(tǒng)。能很好支持除Windows之外的多線程操作,原因是Sun的JRE在windows平臺(tái)上長(zhǎng)期存在的問(wèn)題。
  • NMapDirectory
    使用內(nèi)存映射 I/O進(jìn)行文件訪問(wèn)。對(duì)于64位JRE來(lái)說(shuō)是一個(gè)很好選擇,對(duì)于32位JRE并且索引尺寸相對(duì)較小時(shí)也可以使用該類
  • RAMDirectory
    將所有的文件都存入RAM中,但是不推薦使用于較多索引的情況。會(huì)造成資源的浪費(fèi),以及因?yàn)樗褂?024字節(jié)大小的內(nèi)部緩沖器。
  • FileSwitchDirectory
    使用兩個(gè)文件目錄,根據(jù)文件拓展名在兩個(gè)目錄之間切換使用
2). 核心鎖實(shí)現(xiàn)
  • NativeFSLockFactory
    FSDirectory的默認(rèn)鎖,使用java.nio本地操作系統(tǒng)鎖,在JVM還存在的情況下不會(huì)釋放剩余的被鎖文件。但該鎖可能無(wú)法與一些共享文件系統(tǒng)很好地協(xié)同,特別是NFS文件系統(tǒng)
  • SimpleFSLockFactory
    使用Java的File.createNewFile API,它比NativeFSLockFactory更易于在不同文件系統(tǒng)間移植。
  • SingleInstanceLockFactory
    在內(nèi)存中創(chuàng)建一個(gè)完全的鎖,該類是RAMDirectory默認(rèn)的鎖實(shí)現(xiàn)子類。在程序知道所有IndexWriter將在同一個(gè)JVM實(shí)例化時(shí)使用該類
  • NoLockFactory
    完全關(guān)閉鎖機(jī)制。只有在程序確認(rèn)不需要使用Lucene通暢的鎖保護(hù)機(jī)制時(shí)才能使用它。
3).搜索類
  • TermQuery:對(duì)索引中特定項(xiàng)進(jìn)行搜索,查詢值區(qū)分大小寫(xiě)。
  • TermRangeQuery:索引中各個(gè)Term對(duì)象會(huì)按照字典排序順序進(jìn)行排列,并允許在Lucene的TermRangeQuery對(duì)象提供的范圍內(nèi)進(jìn)行文本項(xiàng)的直接搜索。
  • NumericRangeQuery:在指定的數(shù)字范圍內(nèi)搜索。和TermQuery類一樣,newIntRange方法中的兩個(gè)Boolean參數(shù)表示搜索范圍是(用true表示)否(用false表示)包含起點(diǎn)和終點(diǎn)。
  • PrefixQuery:搜索包含以指定字符串開(kāi)頭的項(xiàng)的文檔。
  • BooleanQuery:可以將各種查詢類型組合成復(fù)雜的查詢方式。
    • BooleanClause.Occur.MUST : 只有匹配該查詢語(yǔ)句的文檔才在考慮之列。
    • BooleanClause.Occur.SHOULD: 該項(xiàng)只是可選項(xiàng)。
    • BooleanClause.Occur.MUST_NOT: 意味著搜索結(jié)果不會(huì)包含任何匹配該查詢子舉的文檔。默認(rèn)允許包含1024個(gè)查詢子句,超過(guò)最大值時(shí),程序會(huì)拋出TooManyClauses異常。
  • PhraseQuery:根據(jù)位置信息定位某個(gè)距離范圍內(nèi)的項(xiàng)所對(duì)應(yīng)的文檔。在匹配的情況下,兩個(gè)項(xiàng)的位置之間所允許的最大間隔距離稱為slop,這里的距離是指項(xiàng)若要按順序組成給定短語(yǔ)鎖需要移動(dòng)位置的次數(shù)。
  • WildcardQuery:使用不完整的、缺少某些字母的項(xiàng)進(jìn)行查詢。*代表0個(gè)或者多個(gè)字母,?代表0個(gè)或者1個(gè)字母。
  • FuzzyQuery:用于匹配與指定項(xiàng)相似的項(xiàng)。
  • MatchAllDocsQuery:匹配索引中的所有文檔。
4). 常用分析器
  • WhitespaceAnalyzer:通過(guò)空格來(lái)分割文本信息,而并不對(duì)生成的語(yǔ)匯單元進(jìn)行其他的規(guī)范化處理。
  • SimpleAnalyzer:首先通過(guò)非字母字符來(lái)分割文本信息,然后將語(yǔ)匯單元統(tǒng)一為小寫(xiě)形式。會(huì)去掉數(shù)字類型的字符,但會(huì)保留其他字符。
  • StopAnalyzer:會(huì)去除英文中的常用單詞(如 the、a等)。
  • StandardAnalyzer:包含大量的邏輯操作來(lái)識(shí)別某些種類的語(yǔ)匯單元,比如公司名稱、E-mail地址以及主機(jī)名稱等。還會(huì)將語(yǔ)匯單元轉(zhuǎn)換為小寫(xiě)形式,并去除停用詞和標(biāo)點(diǎn)符號(hào)。
5). 語(yǔ)匯單元屬性
  • TermAttribute:語(yǔ)匯單元對(duì)應(yīng)的文本
  • PositionIncrementAttribute:位置增量(默認(rèn)值為1)
  • OffsetAttriute:起始字符和終止字符的偏移量
  • TypeAttribute:語(yǔ)匯單元類型(默認(rèn)為單詞)
  • FlagsAttribute:自定義標(biāo)志位
  • PayloadAttribute:每個(gè)語(yǔ)匯單元的byte[]類型有效負(fù)載
6). 主要可用分析器
  • WhitespaceAnalyzer:根據(jù)空格拆分語(yǔ)匯單元
  • SimpleAnalyzer:根據(jù)非字母字符拆分文本,將其轉(zhuǎn)換為小寫(xiě)形式
  • StopAnalyzer:根據(jù)非字母字符拆分文本,然后小寫(xiě)化,再移除停用詞
  • KeywordAnalyzer:將整個(gè)文本作為一個(gè)單一語(yǔ)匯單元處理
  • StandardAnalyzer:基于復(fù)雜的語(yǔ)法來(lái)生成語(yǔ)匯單元,該語(yǔ)法能識(shí)別E-mail地址、首字母縮寫(xiě)詞詞、韓語(yǔ)/漢語(yǔ)/日語(yǔ)等字符、字母數(shù)字等,還能完成小寫(xiě)轉(zhuǎn)換和移除停用詞。
7).索引文件格式
  • 數(shù)據(jù)結(jié)構(gòu)
    索引包含了存儲(chǔ)的文檔(document)正排、倒排信息,用于文本搜索。索引又分為多個(gè)段(segments),每個(gè)新添加的doc都會(huì)存到一個(gè)新segment中,不同的segments又會(huì)合并成一個(gè)segment。segment存儲(chǔ)著具體的documents,每個(gè)doc有一系列的字段組成,一個(gè)field的值是多個(gè)詞(term),一個(gè)term是以一些bytes。其遞進(jìn)關(guān)系如下:
    index -> segments -> documents -> fields -> terms
  • 文件格式
    • 全局文件
      • segments_N:記錄索引的段數(shù)、各段名、各段中文檔數(shù)、刪除數(shù)和更新數(shù)。可能有多個(gè)segments_N文件,最大的N的segments_N是有效文件。
      • segments.gen:記錄當(dāng)前index的代數(shù)(generation),即segments_N的最大N。
      • write.lock:阻止多給我IndexWriter同時(shí)修改索引,一次只能有一個(gè)IndexWriter。
    • 段文件
      • 段描述:
        xxx.si:段的元數(shù)據(jù),如此段的文檔及相關(guān)文件
        xxx.del:刪除的doc
      • field信息:
        xxx.fnm:field names,field名稱、索引方式。存儲(chǔ)域文件的信息
        xxx.fdx:field index,索引xxx.fdt。存儲(chǔ)域數(shù)據(jù)的指針
        xxx.fdt:field data,存儲(chǔ)stored fields
      • term信息
        xxx.tip:term index,xxx.tim的索引,實(shí)現(xiàn)對(duì)xxx.tim的隨機(jī)存取
        xxx.tim:term directory,按字典順序排列的terms,其值指向.doc/.pos
        xxx.doc:倒排列表,term所在的docs、在doc中的頻率
        xxx.pos:倒排列表,term在doc中的位置
        xxx.pay:payloads and offsets,term在doc中的offset
      • term vector
        term vector 用于打分,存儲(chǔ)StoreTermVectors的field
        xxx.tvx:term vector index,每個(gè)doc 在xxx.tvd、xxx.tvf中的位置
        xxx.tvd:term vector data file,每個(gè)doc的term vector field信息在xxx.tvf中的位置
        xxx.tvf:term vector fields,field的term列表及各term的頻率、位置或者偏移
      • 歸一化
        xxx.nvm:norms metadata
        xxx.nvd:norms data
      • doc values
        xxx.dvm:DocValues metadata
        xxx.dvd:DocValues data
      • 復(fù)合文件
        xxx.cfs,xxx.cfe:復(fù)合索引的文件,在系統(tǒng)上虛擬的一個(gè)文件,用于頻繁的文件句柄
      • 詞頻文件
        xxx.frq:詞頻文件,包含文檔列表以及每一個(gè)term和其詞頻

3. Luence 使用

1). 全文檢索過(guò)程
圖1.png
2). 創(chuàng)建文檔對(duì)象

獲取原始內(nèi)容的目的是為了索引,在索引前,需要將原始內(nèi)容創(chuàng)建成文檔(Document),文檔中包括一個(gè)一個(gè)的域(Field),域中存儲(chǔ)內(nèi)容;我們可以將磁盤(pán)上的一個(gè)文件當(dāng)成一個(gè) document, Document 中包括一些Field(file_name 文件名稱, file_path文件路徑, file_size 文件大小, file_content 文件內(nèi)容);一個(gè) Document 可以有多個(gè) Field,同一個(gè)Document,可以有相同的 Field(域名和域值都相同);每一個(gè) Document 都有一個(gè)唯一的編號(hào),就是文檔 id;


圖2.png
3). 分析文檔

將原始內(nèi)容創(chuàng)建為包含域(Field)的文檔(document),需要再對(duì)域中的內(nèi)容進(jìn)行分析,分析的過(guò)程是經(jīng)過(guò)對(duì)原始文檔提取單詞,將字母轉(zhuǎn)為小寫(xiě),去除標(biāo)點(diǎn)符號(hào),去除停用詞等過(guò)程生成最終的語(yǔ)匯單元,可以將語(yǔ)匯單元理解為一個(gè)一個(gè)的單詞;每一個(gè)單詞叫做一個(gè)Term,不同的域中拆分出來(lái)的相同的單詞是不同的term; term中包含兩部分,一部分是文檔的域名, 另一部分是單詞的內(nèi)容。

Field 域的屬性

  • 是否分析: 是否對(duì)域的內(nèi)容進(jìn)行分詞處理;
  • 是否索引: 將 Field 分析后的詞或整個(gè) Field 值進(jìn)行索引,只有建立索引,才能搜索到;
  • 是否存儲(chǔ): 存儲(chǔ)在文檔中的 Field 才可以從 Document 中獲取;


    圖3.png
4). 創(chuàng)建索引

對(duì)所有文檔分析得出的語(yǔ)匯單元進(jìn)行索引,索引的目的是為了搜索,最終要實(shí)現(xiàn)只搜索被索引的語(yǔ)匯單元從而找到 Document,這種索引的結(jié)構(gòu)叫倒排索引結(jié)構(gòu);傳統(tǒng)方法是根據(jù)文件找到該文件的內(nèi)容,在文件內(nèi)容中匹配搜索關(guān)鍵字,這種方法是順序掃描方法,數(shù)據(jù)量大,搜索慢;倒排索引結(jié)構(gòu)是根據(jù)內(nèi)容(詞語(yǔ))找文檔; 順序掃描方法是根據(jù)文檔查找里面的內(nèi)容。


圖4.png

I. 導(dǎo)入jar包
commons-io-2.5.jar、lucene-analyzers-common-7.3.0.jar、lucene-core-7.3.0.jar、lucene-queryparser-7.3.0.jar


圖5.png

II. 測(cè)試代碼

/**
 *  Lucene索引 測(cè)試
 * 使用到的Jar包:
 * commons-io-2.5.jar
 * lucene-analyzers-common-7.3.0.jar
 * lucene-core-7.3.0.jar
 * lucene-queryparser-7.3.0.jar
 * 
 * @author mazaiting
 */
public class IndexTest {
    
    /**
     * 測(cè)試創(chuàng)建索引
     * @throws IOException
     */
    @Test
    public void createIndexTest() throws IOException {
        // 指定索引庫(kù)的存放位置(Directory 對(duì)象)
        Path path = FileSystems.getDefault().getPath("D:\\distribution\\lucene");
        // 1. 創(chuàng)建Directory對(duì)象
        // FSDirectory磁盤(pán)存儲(chǔ); Directory 保存索引
        Directory directory = FSDirectory.open(path);
        // 2. 指定分詞器
        // 基于復(fù)雜的語(yǔ)法來(lái)生成語(yǔ)匯單元,該語(yǔ)法能識(shí)別E-mail地址、首字母縮寫(xiě)詞詞、
        // 韓語(yǔ)/漢語(yǔ)/日語(yǔ)等字符、字母數(shù)字等,還能完成小寫(xiě)轉(zhuǎn)換和移除停用詞
        Analyzer analyzer = new StandardAnalyzer();
        // IndexWriter配置對(duì)象
        IndexWriterConfig config = new IndexWriterConfig(analyzer);
        // 3. 創(chuàng)建IndexWriter對(duì)象
        IndexWriter indexWriter = new IndexWriter(directory, config);
        
        // 4. 指定原始文件的目錄
        File fileDir = new File("G:\\test");
        // 獲取文件夾和文件列表
        File[] fileList = fileDir.listFiles();
        
        // 遍歷
        for (File file : fileList) {
            // 判斷是否為路徑,如果不是路徑則執(zhí)行里面的內(nèi)容
            if (!file.isDirectory()) {
                // 5. 創(chuàng)建文檔對(duì)象
                Document document = new Document();
                
                // 文件名稱
                // 分詞 索引 存儲(chǔ)
                String fileName = file.getName();
                Field fileNameField = new TextField("fileName", fileName, Store.YES);
                
                // 文件大小
                // 分詞 索引 存儲(chǔ)
                long fileSize = FileUtils.sizeOf(file);
                Field fileSizeField = new TextField("fileSize", String.valueOf(fileSize), Store.YES);
                
                // 文件路徑
                // 不分詞 不索引 存儲(chǔ)
                String filePath = file.getPath();
                Field filePathField = new StoredField("filePath", filePath);
                
                // 文件內(nèi)容
                String fileContent = FileUtils.readFileToString(file, "UTF-8");
                Field fileContentField = new TextField("fileContent", fileContent, Store.YES);
                        
                // 添加字段
                document.add(fileNameField);
                document.add(fileSizeField);
                document.add(filePathField);
                document.add(fileContentField);
                
                // 使用IndexWriter對(duì)象將Document對(duì)象寫(xiě)入到索引庫(kù)
                indexWriter.addDocument(document);
            }
        }
        // 關(guān)閉IndexWriter對(duì)象
        indexWriter.close();
    }   
    
}

III. 執(zhí)行測(cè)試代碼
G:\\test目錄文件如下:

圖6.png

在路徑D:\distribution\lucene\多了些文件

圖7.png
5). 查詢索引

用戶輸入查詢關(guān)鍵字執(zhí)行搜索前,需要先創(chuàng)建一個(gè)查詢對(duì)象,查詢對(duì)象中可以指定查詢要搜索的 Field 文檔域,查詢關(guān)鍵字等,查詢對(duì)象會(huì)生成具體的查詢語(yǔ)法;根據(jù)查詢語(yǔ)法在倒排索引詞典表中分別找出對(duì)應(yīng)搜索詞的索引,從而找到索引所鏈接的文檔鏈表。


圖7.png

I. 測(cè)試代碼

/**
 * Lucene索引 測(cè)試
 * 使用到的Jar包:
 * commons-io-2.5.jar
 * lucene-analyzers-common-7.3.0.jar
 * lucene-core-7.3.0.jar
 * lucene-queryparser-7.3.0.jar
 * 
 * @author mazaiting
 */
public class IndexTest {
    /**
     * 查詢索引
     * 步驟:
     *  1. 創(chuàng)建一個(gè)Directory對(duì)象,用于指定索引庫(kù)存放的位置
     *  2. 創(chuàng)建一個(gè)IndexReader對(duì)象,需要指定Directory對(duì)象,用于讀取索引庫(kù)中的文件
     *  3. 創(chuàng)建一個(gè)IndexSearcher對(duì)象,需要指定IndexReader對(duì)象
     *  4. 創(chuàng)建一個(gè)TermQuery對(duì)象,指定查詢的域和查詢的關(guān)鍵詞
     *  5. 執(zhí)行查詢
     *  6. 返回查詢結(jié)果,遍歷查詢結(jié)果并輸出
     *  7. 關(guān)閉IndexReader    
     * @throws IOException 
     */
    @Test
    public void searchIndexTest() throws IOException {
        // Directory, 指定索引庫(kù)存放的位置
        Path path = FileSystems.getDefault().getPath("D:\\distribution\\lucene");
        Directory directory = FSDirectory.open(path);
        // IndexReader, 讀取索引庫(kù)中的文件
        IndexReader indexReader = DirectoryReader.open(directory);
        // IndexSearcher, 用于查詢
        IndexSearcher indexSearcher = new IndexSearcher(indexReader);
        // TermQuery, 指定查詢的域和查詢的關(guān)鍵詞
        Query query = new TermQuery(new Term("fileName", "java.txt"));
        // 執(zhí)行查詢
        TopDocs topDocs = indexSearcher.search(query, 100);
        // 獲取數(shù)組
        ScoreDoc[] scoreDocs =  topDocs.scoreDocs;
        System.out.println(scoreDocs.length);
        // 遍歷結(jié)果文檔
        for (ScoreDoc scoreDoc : scoreDocs) {
            // 獲取文檔id
            int docId = scoreDoc.doc;
            // 通過(guò)id從索引中獲取對(duì)應(yīng)的文檔
            Document document = indexReader.document(docId);
            // 獲取文件名稱
            String fileName = document.get("fileName");
            // 獲取文件路徑
            String filePath = document.get("filePath");
            // 獲取文件大小
            String fileSize = document.get("fileSize");
            // 獲取文件內(nèi)容
            String fileContent = document.get("fileContent");
            System.out.println("==========================================");
            System.out.println("文件名:" + fileName + "\n"
                    + "文件大?。?" + fileSize + "\n"
                    + "文件路徑:" + filePath + "\n"
                    + "文件內(nèi)容:" + fileContent);
            
        }
        // 關(guān)閉IndexReader
        indexReader.close();
    }
    
}

II. 執(zhí)行測(cè)試代碼
打印結(jié)果:


圖8.png
6). 分詞器

支持中文的分詞器: IKAnalyzer
從一個(gè) Reader 字符流開(kāi)始,創(chuàng)建一個(gè)基于 Reader 的 Tokenizer分詞器,經(jīng)過(guò)三個(gè) TokenFilter,生成語(yǔ)匯單元 Tokens。

圖9.png

I. 代碼

/**
 * Lucene索引 測(cè)試
 * 使用到的Jar包:
 * commons-io-2.5.jar
 * lucene-analyzers-common-7.3.0.jar
 * lucene-core-7.3.0.jar
 * lucene-queryparser-7.3.0.jar
 * 
 * @author mazaiting
 */
public class IndexTest {
    
    /**
     * 查看標(biāo)準(zhǔn)分詞器的分詞效果
     * @throws IOException 
     */
    @Test
    public void analyzerTest() throws IOException {
        // 創(chuàng)建一個(gè)標(biāo)準(zhǔn)分詞器對(duì)象
        Analyzer analyzer = new StandardAnalyzer();
        // 獲得TokenStream對(duì)象
        // 參數(shù)1: 字段名,可以隨便給;參數(shù)2: 要分析的文本內(nèi)容
        TokenStream tokenStream = analyzer.tokenStream("test", 
                "The Spring Framework provides a comprehensive programming and configuration model.");
        // 添加引用,可以獲得每個(gè)關(guān)鍵字
        CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class);
        // 添加一個(gè)偏移量的引用,記錄了關(guān)鍵詞的開(kāi)始位置及結(jié)束位置
        OffsetAttribute offsetAttribute = tokenStream.addAttribute(OffsetAttribute.class);
        // 將指針調(diào)整到列表的頭部
        tokenStream.reset();
        // 遍歷關(guān)鍵詞列表,通過(guò)incrementToken方法判斷列表是否結(jié)束
        while (tokenStream.incrementToken()) {
            // 關(guān)鍵詞其實(shí)位置
            System.out.println("start->" + offsetAttribute.startOffset());
            // 取關(guān)鍵詞
            System.out.println(charTermAttribute);
            // 結(jié)束位置
            System.out.println("end->" + offsetAttribute.endOffset());          
        }
        // 關(guān)閉
        tokenStream.close();
        analyzer.close();
    }
}

II. 執(zhí)行測(cè)試


圖10.png
7). 索引庫(kù)維護(hù)工具類
/**
 * 索引庫(kù)維護(hù)工具類
 * @author mazaiting
 */
public class LuceneManager {
    /**
     * 獲取IndexWriter對(duì)象
     * @return
     */
    public IndexWriter getIndexWriter() {
        try {
            // 獲取索引庫(kù)路徑
            Path path = FileSystems.getDefault().getPath("D:\\distribution\\lucene");
            // 創(chuàng)建索引庫(kù)字典
            Directory directory = FSDirectory.open(path);
            // 創(chuàng)建分析器
            Analyzer analyzer = new StandardAnalyzer();
            // 創(chuàng)建IndexWriter配置
            IndexWriterConfig config = new IndexWriterConfig(analyzer);
            return new IndexWriter(directory, config);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
    
    /**
     * 全部刪除 
     * @throws IOException
     */
    @Test
    public void delAllTest() throws IOException {
        IndexWriter writer = getIndexWriter();
        writer.deleteAll();
        writer.close();
    }
    
    /**
     * 根據(jù)條件刪除
     * @throws IOException 
     */
    @Test
    public void delTest() throws IOException {
        IndexWriter writer = getIndexWriter();
        Query query = new TermQuery(new Term("fileName", "java"));
        writer.deleteDocuments(query);
        writer.close();
    }
    
    /**
     * 更新
     * @throws IOException 
     */
    @Test
    public void update() throws IOException {
        IndexWriter writer = getIndexWriter();
        Document document = new Document();
        document.add(new TextField("fileName", "測(cè)試文件名", Store.YES));
        document.add(new TextField("fileContent", "測(cè)試文件內(nèi)容", Store.YES));
        
        // 將lucene刪除, 然后添加
        writer.updateDocument(new Term("fileName", "lucene"), document);
        writer.close();
    }
    
}
8). 索引庫(kù)查詢

對(duì)要搜索的信息創(chuàng)建 Query 查詢對(duì)象,Lucene會(huì)根據(jù) Query 查詢對(duì)象生成最終的查詢語(yǔ)法;
可通過(guò)兩種方法創(chuàng)建查詢對(duì)象:

  • 使用 Lucene 提供的 Query子類;
  • 使用 QueryParse 解析查詢表達(dá)式, 需要加入lucene-queryparser-7.3.0.jar
/**
 * 索引庫(kù)維護(hù)工具類
 * 
 * @author mazaiting
 */
public class LuceneManager {
    /**
     * 獲取IndexSearcher
     * 
     * @return
     */
    public IndexSearcher getIndexSearcher() {
        try {
            // 獲取索引庫(kù)路徑
            Path path = FileSystems.getDefault().getPath("D:\\distribution\\lucene");
            // 創(chuàng)建索引庫(kù)字典
            Directory directory = FSDirectory.open(path);
            // 創(chuàng)建索引讀取者
            IndexReader indexReader = DirectoryReader.open(directory);
            return new IndexSearcher(indexReader);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 獲取執(zhí)行結(jié)果
     * 
     * @throws IOException
     */
    public void printResult(IndexSearcher indexSearcher, Query query) throws IOException {
        // 執(zhí)行查詢
        TopDocs topDocs = indexSearcher.search(new TermQuery(new Term("fileName")), 10);
        // 獲取數(shù)組
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        System.out.println(scoreDocs.length);
        // 遍歷結(jié)果文檔
        for (ScoreDoc scoreDoc : scoreDocs) {
            // 獲取文檔id
            int docId = scoreDoc.doc;
            // 通過(guò)id從索引中獲取對(duì)應(yīng)的文檔
            Document document = indexSearcher.doc(docId);
            // 獲取文件名稱
            String fileName = document.get("fileName");
            // 獲取文件路徑
            String filePath = document.get("filePath");
            // 獲取文件大小
            String fileSize = document.get("fileSize");
            // 獲取文件內(nèi)容
            String fileContent = document.get("fileContent");
            System.out.println("==========================================");
            System.out.println("文件名:" + fileName + "\n" + "文件大?。?" + fileSize + "\n" + "文件路徑:" + filePath + "\n"
                    + "文件內(nèi)容:" + fileContent);

        }
    }
    
    /**
     * 查詢所有
     * @throws IOException 
     */
    @Test
    public void matchAllDocsQueryTest() throws IOException {
        // 獲取查詢索引對(duì)象
        IndexSearcher indexSearcher = getIndexSearcher();
        // 查詢所有
        Query query = new MatchAllDocsQuery();
        // 打印結(jié)果
        printResult(indexSearcher, query);
        // 關(guān)閉資源
        indexSearcher.getIndexReader().close();
    }
    
    /**
     * 組合查詢
     * @throws IOException 
     */
    @Test
    public void boolQueryTest() throws IOException {
        // 創(chuàng)建搜索
        IndexSearcher indexSearcher = getIndexSearcher();
        // 創(chuàng)建查詢
        Query query1 = new TermQuery(new Term("fileName", "java.txt"));
        Query query2 = new TermQuery(new Term("fileName", "c.txt"));
        // 構(gòu)建表達(dá)式 
        // Occur.MUST: 必須滿足此條件, 相當(dāng)于 and
        // Occur.SHOULD: 應(yīng)該滿足此條件, 但是不滿足也可以, 相當(dāng)于 or
        // Occur.MUST_NOT: 必須不滿足, 相當(dāng)于 not
        BooleanClause clause1 = new BooleanClause(query1, Occur.SHOULD);
        // Build模式創(chuàng)建
        BooleanQuery.Builder builder = new BooleanQuery.Builder();
        // 添加表達(dá)式
        builder.add(clause1);
        // 添加查詢
        builder.add(query2, Occur.SHOULD);
        // 打印
        printResult(indexSearcher, builder.build());
        // 關(guān)閉資源
        indexSearcher.getIndexReader().close();
    }
    
    /**
     * 使用QueryParser解析查詢表達(dá)式
     * @throws ParseException 
     * @throws IOException 
     */
    @Test
    public void queryParserTest() throws ParseException, IOException {
        IndexSearcher indexSearcher = getIndexSearcher();
        // 創(chuàng)建QueryParser對(duì)象,其中參數(shù)一:字段名,參數(shù)而分詞器
        QueryParser queryParser = new QueryParser("fileName", new StandardAnalyzer());
        // 此時(shí):表示使用默認(rèn)域:fileName
        // 查詢fileContent域
        Query query = queryParser.parse("fileContent:apache");
        // 打印
        printResult(indexSearcher, query);
        // 關(guān)閉資源
        indexSearcher.getIndexReader().close();
    }
    
    /**
     * 指定多個(gè)默認(rèn)搜索域
     * @throws ParseException 
     * @throws IOException 
     */
    @Test
    public void multiFieldQueryParser() throws ParseException, IOException {
        IndexSearcher indexSearcher = getIndexSearcher();
        // 指定多個(gè)默認(rèn)搜索域
        String[] fields = {"fileName", "fileContent"};
        // 創(chuàng)建MultiFieldQueryParser對(duì)象
        MultiFieldQueryParser queryParser = new MultiFieldQueryParser(fields, new StandardAnalyzer());
        // 創(chuàng)建查詢
        Query query = queryParser.parse("apache");
        // 輸出查詢條件
        System.out.println(query);
        // 執(zhí)行查詢
        printResult(indexSearcher, query);
        // 關(guān)閉資源
        indexSearcher.getIndexReader().close();
    }
}

代碼下載

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容