Lucene筆記1-建立索引

構(gòu)建索引過程

文檔是Lucene索引和被搜索的最小單位效诅,一個文檔包含一個或者多個域彼绷,而域則包含了真正 被搜索 的內(nèi)容余爆,每個域都有一個標(biāo)識名稱(如標(biāo)題,描述)對應(yīng)一個值(比如 標(biāo)題:lucene)

建立了文檔和域以后硕舆,就可以調(diào)用IndexWriter的addDocument方法

倒排索引

其實是表示 秽荞,哪些文檔包含單詞X ,而不是這個文檔包含哪些單詞

基礎(chǔ)入門

既然是索引抚官,那么肯定需要有地方存儲他們扬跋,Lucene提供了多種存儲索引的方式,Lucene在文件系統(tǒng)中存儲索引的最基本的抽象實現(xiàn)類是BaseDirectory凌节,其中最常使用的是FSDirectory 和 RAMDirectory钦听,前者是主要用來存儲到文件到文件系統(tǒng)(其中有幾個子類,實現(xiàn)不同的存儲策略刊咳,包括Nio,內(nèi)存映射等等)彪见,后者是直接存儲到內(nèi)存中儡司,適合小型應(yīng)用或者實驗學(xué)習(xí)性質(zhì)的Demo娱挨,如果數(shù)據(jù)量較大的話,內(nèi)存會吃不住的(官方文檔表示捕犬,20G原始數(shù)據(jù)跷坝,大概需要4-6GB的索引結(jié)構(gòu)數(shù)據(jù))

使用如下:

valindexConfig:IndexWriterConfig  = new IndexWriterConfig(new StandardAnalyzer());

indexConfig.setOpenMode(IndexWriterConfig.OpenMode.CREATE_OR_APPEND)

//  indexConfig.setInfoStream(System.out)

val directory:Directory = FSDirectory.open(Paths.get(indexPath))

val indexWriter:IndexWriter = new IndexWriter(directory,indexConfig)

這樣就成功實例化了一個IndexWriter,可以對索引進(jìn)行寫操作碉碉,IndexWriter負(fù)責(zé)創(chuàng)建索引和打開已經(jīng)存在的索引柴钻,向其中更新和刪除索引,不能夠用作讀垢粮,如果開辟內(nèi)存空間贴届,則需要Directory來完成,因為底層的IO抽象在Directory中,不同的場景需要使用不同的Directory實現(xiàn)毫蚓。同時占键,也要對IndexWriter進(jìn)行一些配置,比如設(shè)定分析器為 StandardAnalyzer ,設(shè)定文件操作模式等等元潘,這需要使用IndexWriterConfig 類(具體操作文檔 http://lucene.apache.org/core/6_4_0/core/org/apache/lucene/index/IndexWriterConfig.html

索引寫入對象準(zhǔn)備好以后畔乙,就可以開始構(gòu)建索引了,翩概,要構(gòu)建索引牲距,首先要了解索引的相關(guān)概念,在Lucene中索引相關(guān)的概念如下:

Lucene概念

傳統(tǒng)數(shù)據(jù)庫概念

備注

IndexSearcher

table钥庇,讀取的句柄

IndexWriter

table牍鞠,寫入的句柄

Directory

底層IO寫入的句柄

描述了Lucene索引的存放位置,它的子類負(fù)責(zé)具體指定索引的存儲路徑

派生出

FSDirectory评姨,RAMDirectory等

DirectoryReader

底層IO讀取句柄 讀取Directory

Document

一條記錄

代表一些域(Field)的集合皮服,你可以將Document對象理解為虛擬文檔-例如Web頁面、E-mail信息或者文本文件

Field

每個字段

分為可被索引的参咙,可切分的龄广,不可被切分的,不可被索引的幾種組合類型

Hits

RecoreSet

結(jié)果集

Analyzer

分析器

負(fù)責(zé)文本分析蕴侧,從被索引文本文件中提取出語匯單元择同。對于文本分析器Analyzer,需要注意一點净宵,就是使用哪種Analyzer進(jìn)行索引創(chuàng)建敲才,查詢的時候也要使用哪種Analyzer查詢,否則查詢結(jié)果不正確择葡。

FieldType

域類型紧武,每一個Field存儲類型

描述了Field的各種屬性,在不使用某種具體的Field類型(例如StringField敏储,TextField)時需要用到此類

也就是說一條索引的記錄就是一個document浇雹,比如某篇文章裕膀,它的全部信息就可以看作為一個document,而其中的作者,標(biāo)題祝旷,編號结啼,摘要就可以看做是各個Field贞滨,如果需要寫索引拙吉,就要通過IndexWriter實例化的對象去操作。如果需要搜索結(jié)果缆蝉,就需要 IndexSearcher 實例宇葱,搜索后得到hits結(jié)果集瘦真。

創(chuàng)建索引

創(chuàng)建索引具體實現(xiàn)的代碼如下:

def createIndex(mapList: Array[Map[String,String]]): Unit ={

val fieldType = new FieldType()

fieldType.setIndexOptions(IndexOptions.DOCS_AND_FREQS_AND_POSITIONS)

fieldType.setStored(true)

fieldType.setTokenized(true)

for(a <- 0 until mapList.length){

var documentation = new Document();

var single = mapList(a)

documentation.add(new Field("goods_id",single("goods_id"),fieldType))

documentation.add(new Field("goods_name",single("goods_name"),fieldType))

documentation.add(new Field("goods_price",single("goods_price"),fieldType))

documentation.add(new Field("goods_seller",single("goods_seller"),fieldType))

indexWriter.addDocument(documentation)

}

indexWriter.commit()

}

//測試調(diào)用

deftestCreateIndex(): Unit ={

val index = new Index("./index_store")

val arrayList = Array(

Map("goods_id" -> "sa2a", "goods_name" -> "xs", "goods_price" -> "19.22", "goods_seller" -> "A&TT")

)

index.createIndex(arrayList)

index.close()

}

一個域是屬于一個document的,一個document可以包含多個域黍瞧,可以把document理解為數(shù)據(jù)庫中的一行吗氏,而Field是其中的字段,操作如下:

var documentation = new Document();

documentation.add(new Field("goods_id",single("goods_id"),fieldType))

document添加的時候接受一個實現(xiàn) IndexableField 接口對象雷逆,F(xiàn)ield 實現(xiàn)了 IndexableField弦讽,所以直接創(chuàng)建一個Field即可,完成document操作后膀哲,將其寫入到索引中往产,并提交:

indexWriter.addDocument(documentation)

indexWriter.commit()

這樣就是一個完整的索引建立過程

域類型

首先先創(chuàng)建了FieldType,定義Field的類型某宪,這里定義為以為存儲和Tokenized仿村。

這些字段類型可以由幾個成員函數(shù)調(diào)用來配置

1.Stored表示要存儲到索引中,要配置為Stored兴喂,調(diào)用 setStored(boolean value) 蔼囊,通常我們只存儲一些短小精悍且必要的字段,像標(biāo)題衣迷,id畏鼓,url這種,而文章正文這樣的大篇幅數(shù)據(jù)一般不存儲與索引壶谒。

  1. 如果要這個Field的值在進(jìn)入之前先通過分析器過濾調(diào)用setTokenized(boolean value)

3.設(shè)定索引的類型使用 setIndexOptions(IndexOptions value) 云矫,需要傳入索引參數(shù),是一個枚舉汗菜,有這些值如下

DOCS

只有文檔會被索引让禀,詞頻和位置都會被省略

DOCS_AND_FREQS

文檔和詞頻被索引,位置省略

DOCS_AND_FREQS_AND_POSITIONS

文檔 詞頻 位置都被索引

DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS

除了文檔和詞頻位置還有偏移量也會被索引

NONE

不索引

有些字段我們需要在查詢的時候返回陨界,但不希望它進(jìn)入索引影響查詢效率巡揍,可以用setIndexOptions設(shè)為False,同時有些詞我們只需要對其進(jìn)行過濾即可菌瘪,比如權(quán)限和時間過濾腮敌,這種值我們不太需要記錄其出現(xiàn)的頻率(詞頻)和位置(偏移量),所以只需要DOCS級別即可麻车,通常對于要索引的字段我們都設(shè)置為DOCS_AND_FREQS_AND_POSITIONS.

匯總起來缀皱,操作代碼如下:

val fieldType = new FieldType()

fieldType.setIndexOptions(IndexOptions.DOCS_AND_FREQS_AND_POSITIONS)

fieldType.setStored(true)

fieldType.setTokenized(true)

設(shè)定好FieldType后,就可以用類型去創(chuàng)建域动猬,代碼如下

var f1 = new Field("goods_id",single("goods_id"),fieldType)

注意,老版本的Lucene是通過存儲選項+索引選項+項向量組合起來決定一個Field的性質(zhì)表箭,調(diào)用起來比較復(fù)雜赁咙,新版使用一個新的類FieldType并使用一些方法來設(shè)定,比較清晰和方便

多值域問題

在某些場景下,我們需要一個作用域有多個值彼水,比如作者信息崔拥,很多時候作者不止一個人,需要向一個Field里面寫入多個值凤覆,這種情況只需要直接向一個Document寫入多個相同名字相同但是值不同的Field即可链瓦,至于這些Field該如何定義優(yōu)先級,可以在分析的時候進(jìn)行干預(yù)(見后文)盯桦。

Lucene處理Field的時候還設(shè)計了針對多種IO的構(gòu)造函數(shù)慈俯,除了string外,TokenStream/Reader還可以針對占用內(nèi)存空間較大的Field進(jìn)行分析拥峦,避免一次讀入占用內(nèi)存

加權(quán)

加權(quán)操作是認(rèn)為的對結(jié)果進(jìn)行干預(yù)贴膘,可以在索引期間完成,也可以在搜索期間完成略号,這里著重描述如何在索引期間加權(quán)刑峡。

調(diào)用加權(quán)的操作在3.0+版本上可以在一個Document上面進(jìn)行,但在6.0+版本上只在文檔中找到了基于Field的操作玄柠,如下

var goods_id = new Field("goods_id",single("goods_id"),fieldType)

goods_id.setBoost(1.5F)

加權(quán)值高的Field會更比較低的更加優(yōu)先被搜索到突梦,Lucene通過查詢語句的匹配程度來對搜索結(jié)果進(jìn)行排名,每個匹配的文檔都有一個評分羽利,加權(quán)數(shù)是評分的一個重要因素阳似。

Lucene會基于域的語匯單元來計算加權(quán)值(更短的域有較高的加權(quán),這里隱含了如果越短铐伴,則優(yōu)先級可能越高)撮奏,這些加權(quán)會被合并量化為一個單一的字節(jié)值(加權(quán)基準(zhǔn)Norms),并且存儲当宴,在搜索的時候被加載到內(nèi)存畜吊,還原為浮點數(shù),然后用于計算評分户矢。Norms可以在搜索時候用IndexReader.setNorm進(jìn)行修改.

對于域比較多的文檔來說玲献,加載norms信息會占用大量內(nèi)存空間,可以在FieldType進(jìn)行設(shè)定梯浪,關(guān)閉norms相關(guān)操作捌年。

fieldType.setOmitNorms(false)

索引非字符串類型

很多場景下都需要索引數(shù)字類型,比如價格挂洛,時間等礼预,一種情況是數(shù)字包含在文字中比如‘我買50塊錢的東西’,50要被索引虏劲,需要選擇一個不丟棄數(shù)字的分析器托酸,比如StandardAnalyzer(而SimpleAnalyzer和StopAnalyzer是反例褒颈,他們會剔除數(shù)字),這樣就可以達(dá)到想要的目標(biāo)励堡。另一種情況是我們直接就想索引一個數(shù)字谷丸,這就需要使用IntPoint等數(shù)據(jù)類型,他們是Field派生出的子類应结,標(biāo)準(zhǔn)文檔上給出了這些Field:

BinaryDocValuesField, BinaryPoint, DoublePoint, FloatPoint, IntPoint, LegacyDoubleField, LegacyFloatField, LegacyIntField, LegacyLongField, LongPoint, NumericDocValuesField, SortedDocValuesField, SortedNumericDocValuesField, SortedSetDocValuesField, StoredField, StringField, TextField

var price = new IntPoint("price",15,fieldType)

price.setIntValues(15)//也可以通過這種方式進(jìn)行修改

由于這些類型都是繼承Field刨疼,所以執(zhí)行Field相關(guān)的方法,同上鹅龄,我們也可以對一個域添加多個值揩慕,在搜索的時候?qū)@些值的處理方式是or關(guān)系,且排序是不確定的砾层。int也可以處理時間漩绵,將時間轉(zhuǎn)換為Int即可(更精確的時間可以使用LongPoint)。

Field截取

對于一些尺寸未知的文件肛炮,我們需要進(jìn)行截取止吐,從而控制內(nèi)存和硬盤的使用量,對一個IndexWriter調(diào)用API來實現(xiàn)setMaxFieldLength todo找到文檔

實時搜索

很多時候修改了索引以后需要馬上看到效果侨糟,但從新New一個IndexReader會非常的耗時碍扔,3.0+版本讓我們使用indexWriter . getReader(),但目前這個接口已經(jīng)被標(biāo)記為廢棄

getReader(int termInfosIndexDivisor)

Deprecated. Please use IndexReader.open(IndexWriter,boolean) instead. Furthermore, this method cannot guarantee the reader (and its sub-readers) will be opened with the termInfosIndexDivisor setting because some of them may have already been opened according to IndexWriterConfig.setReaderTermsIndexDivisor(int). You should set the requested termInfosIndexDivisor through IndexWriterConfig.setReaderTermsIndexDivisor(int) and use getReader().

我們直接使用IndexReader從新打開IndexWriter即可。

索引優(yōu)化

當(dāng)多次對一個索引進(jìn)行寫操作時秕重,會產(chǎn)生很多獨立的段不同,當(dāng)搜索時,lucene必須單獨搜索每個段溶耘,然后合并段的搜索結(jié)果二拐,當(dāng)處理大量數(shù)據(jù)時,需要盡量合并這些段凳兵,早起indexWriter提供了一些api百新,不過現(xiàn)在已經(jīng)廢棄,參考https://lucene.apache.org/core/3_5_0/api/core/org/apache/lucene/index/IndexWriter.html#optimize()庐扫,目前indexWriter/IndexReader會自動進(jìn)行優(yōu)化饭望。

Directory子類介紹

前面我們簡單介紹了Directory,這里深入描述下幾種Directory子類的差別和使用范圍:

  1. SimpleFSDirectory: 直接使用java.io操作文件系統(tǒng)形庭,不能很好的支持多線程铅辞,如要要做到就必須使用外部加鎖,并且不支持按位置讀取萨醒。

2.NIOFSDirectory:使用java nio進(jìn)行文件操作斟珊,異步進(jìn)行,可以很好的支持多線程讀取

3.MMapDirectory:內(nèi)存映射io進(jìn)行文件訪問验靡,不需要用鎖機(jī)制就可以在多線程下很好的運行倍宾,但由于內(nèi)存映射IO消耗的地址空間和索引尺寸是相等雏节,所以在32位jvm上比較雞肋(也就是說索引最多只能4G)胜嗓,推薦64位使用高职,不過由于JVM沒有取消映射關(guān)系的機(jī)制,所以只有在垃圾回收的時候辞州,才會釋放內(nèi)存空間和文件描述符怔锌,會造成一些困擾。

4.RAMDirectory:直接存在內(nèi)存变过,實驗用埃元。

作用用戶調(diào)用我們不用太糾結(jié)選擇,直接使用FSDirectory即可媚狰,他會根據(jù)當(dāng)前環(huán)境來選擇最適合的方式岛杀,如果需要自定,可以考慮自己實例化相關(guān)的類崭孤。

并發(fā)

Lucene在并發(fā)方面有以下的特性:

1.任意只讀的IndexReader可以同時打開一個索引类嗤,無論這些Reader是否在一臺機(jī)器上。最好的辦法是一個jvm內(nèi)只有一個Reader辨宠,多個線程共享進(jìn)行搜索

2.對于一個索引一次只能打開一個Writer遗锣,Lucene提供一個文件鎖來保障這一特性。

3.IndexReader可以在Writer進(jìn)行寫入的時候打開嗤形,他可以看到IndexWriter提交之前的數(shù)據(jù)精偿。

4.IndexReader是線程安全的

可以通過IndexWriter.isLocked來判斷是否有鎖,也有一些方法來自定義鎖的實現(xiàn)赋兵。

參考:

Lucene6.4文檔 http://lucene.apache.org/core/6_4_0/core/index.html

Lucene3.5文檔 https://lucene.apache.org/core/3_5_0/api/core/overview-summary.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末笔咽,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子霹期,更是在濱河造成了極大的恐慌叶组,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件经伙,死亡現(xiàn)場離奇詭異扶叉,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)帕膜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,347評論 3 385
  • 文/潘曉璐 我一進(jìn)店門枣氧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人垮刹,你說我怎么就攤上這事达吞。” “怎么了荒典?”我有些...
    開封第一講書人閱讀 157,435評論 0 348
  • 文/不壞的土叔 我叫張陵酪劫,是天一觀的道長吞鸭。 經(jīng)常有香客問我,道長覆糟,這世上最難降的妖魔是什么刻剥? 我笑而不...
    開封第一講書人閱讀 56,509評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮滩字,結(jié)果婚禮上造虏,老公的妹妹穿的比我還像新娘。我一直安慰自己麦箍,他們只是感情好漓藕,可當(dāng)我...
    茶點故事閱讀 65,611評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著挟裂,像睡著了一般享钞。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上诀蓉,一...
    開封第一講書人閱讀 49,837評論 1 290
  • 那天栗竖,我揣著相機(jī)與錄音,去河邊找鬼交排。 笑死划滋,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的埃篓。 我是一名探鬼主播处坪,決...
    沈念sama閱讀 38,987評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼架专!你這毒婦竟也來了同窘?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,730評論 0 267
  • 序言:老撾萬榮一對情侶失蹤部脚,失蹤者是張志新(化名)和其女友劉穎想邦,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體委刘,經(jīng)...
    沈念sama閱讀 44,194評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡丧没,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,525評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了锡移。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片呕童。...
    茶點故事閱讀 38,664評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖淆珊,靈堂內(nèi)的尸體忽然破棺而出夺饲,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 34,334評論 4 330
  • 正文 年R本政府宣布往声,位于F島的核電站擂找,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏浩销。R本人自食惡果不足惜贯涎,卻給世界環(huán)境...
    茶點故事閱讀 39,944評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望撼嗓。 院中可真熱鬧柬采,春花似錦欢唾、人聲如沸且警。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,764評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽斑芜。三九已至,卻和暖如春祟霍,著一層夾襖步出監(jiān)牢的瞬間杏头,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,997評論 1 266
  • 我被黑心中介騙來泰國打工沸呐, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留醇王,地道東北人。 一個月前我還...
    沈念sama閱讀 46,389評論 2 360
  • 正文 我出身青樓崭添,卻偏偏與公主長得像寓娩,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子呼渣,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,554評論 2 349

推薦閱讀更多精彩內(nèi)容