【轉載】Lucene學習筆記(三)

三、用Lucene建立索引:
大綱:

1. Lucene索引的建立過程以及相關技術的簡介
2. Lucene的文檔格式
3. Lucene索引的優化
4. Lucene索引的同步機制
5. Lucene索引的格式

1. 索引建立的過程:大致分爲以下四步"提取文本"、"構建Document"、"分析"和"建立索引"。

1.1 提取文本:爲了使Lucene對文檔數據建立索引,第一步就是要把這些要建立索引的文檔數據轉換成Lucene可以處理的類型!
在之前我們處理的要建立索引的文檔都是純文本類型的,這樣做是爲了省去一些比較賦值的麻煩。因爲實際情況中會有各種格式的文檔,
比如文檔格式是PDF格式,如果想對它建立索引,那麼首先必須從PDF文檔中提取出文本內容,然後Lucene纔可以對這些文本內容建立索引
因爲Lucene只能對文本內容建立索引。

1.2 構建Document:第一步已經提取出文本內容,但是並沒有建立索引,而是進入第二步構建Document對象(還需要Field對象),
構建完Document對象也是爲建立索引做準備。<<至於構建Document對象的具體內容下邊2標題中會明確講解>>

1.3 分析與建立索引:完成前兩步之後,就要調用IndexWriter對象的addDocument()方法使用Lucene建立索引。
其實再調用的addDocument()方法內部並不是立即就建立索引,而是先對要建立索引的數據進行分析(analysis),也即進行分詞處理,
然後索引寫入器會按照Lucene規定的索引格式將分詞器處理完的數據寫入索引文件,進而完成建立索引的過程!

2. Lucene的文檔格式:就是說要建立索引的文檔,加載到Lucene的程序中將會呈現的格式,
就是我們前面一直提到的Document(文檔)和Field(字段)。
Document和Field在Lucene的索引過程中扮演着舉足輕重的位置,同時在Lucene的搜索部分也會涉及到相應的概念,
因此可以說深入理解Document和Field是使用Lucene的基礎。

2.1 Document(文檔):可以說一個文檔文本將會對應一個Document對象,因爲,
從建立索引的角度說,將會把一個文件中的內容封裝成爲一個Document對象;
從搜索功能的角度說,返回的搜索結果是Document對象的集合,也就是說搜索到的每一個匹配者對應一個Document對象
所以要建立索引的文檔與Document對象應該是一對一的關係!!

2.1.1 數據源的確立:對於一個文檔來說,它提供的數據源可以是文件名、文件內容、文件最後一次修改的日期等。所以對於一個文檔可以
提供多個數據源。因爲一個文檔對應一個Document對象,所以一個Document對象應該是多個數據源的集合,正因如此Lucene提供了Field對象,
用來分裝每個數據源,也就是Document對象通過保存Field對象來實現對多個數據源的保存(文件名、文件內容等)。

2.1.2 換一個角度說:Lucene其實是對Document對象建立索引(因爲是將要建立索引的文檔的文本內容提取出來構建Document對象)

2.1.3 例子:將提取一個網頁文檔內容封裝成一個Document對象
//創建一個空內容的Document對象
Document document = new Document();
//添加文檔的路徑信息到"path"字段(Field對象)中,然後加入到Document對象
Field pathField = Field.Keyword("path",fileObject.getPath());
document.add(pathField);
//添加文檔最後一次修改時間信息到"lastModified"字段,這裏用了DateField.timeToString方法對時間進行了轉換,因爲f.lastModified返回毫秒數
document.add(Field.Keyvalue("lastModified", DateField.timeToString(fileObject.lastModified()));
//添加文檔內容到"content"字段
BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(fileObject)));
document.add("content", reader);
//構建Document對象結束

**注意:代碼中我們提到了path、lastModified和content字段,可以認爲這是對應的Field對像的名字,
這些Field字段對象,在搜索的時候會有相應的作用。
想更一步瞭解Document對象的話可以查看Document類的源代碼!
其實Document內部使用了一個List fields = new Vector();集合來保存多個Field對象。

2.1.4 使用Document的好處:使用Document來表示Lucene的一個抽象文檔,好處就是它不和任何類型的文件相關聯,
它是把對應文檔中的數據進行了封裝,無論要建立索引的文檔類型原本是什麼,Lucene都不用關心,
因爲已經得到Document對象了就代表已經完成了從要建立索引的文檔中提取數據信息的步驟,
到此步驟Lucene只管操作Document就可以建立索引了!

2.2 Field(字段):Field(字段)是與Document(文檔)緊密相連的一個概念,在一個Document中,它代表了不同數據源的名稱和對應的數據值,
即Field內封裝的name屬性表示名稱,value屬性代表的是數據源值;比如一個文件對應一個Document對象,文件中的數據源包括文件名、內容,
分別用Field對象來保存,Field.Textvalue("fileName",文件名);

2.2.1 然而實際中每個數據源提供的數據都不是一成不變的,就拿文件的內容這種數據源來說吧,一個文件中的內容可能會隨時變化,
所以對於不同的數據源,即說成對於不同的字段,我們會需要Lucene提供不同的處理方式!
爲此Lucene提供了三種處理方式:"是否切詞"、"是否索引"、"是否存儲"

2.2.1.1 是否切詞:表示在該Field中的數據是否需要被切詞(有關具體解釋後面章節會講解)

2.2.1.2 是否索引:表示在該Field中的數據是否在將來檢索時需要被用戶檢索到。

**注意:如果不索引,只是用戶通過該字段搜索的時候會搜不到對應該字段的數據,
即在建立索引的時候不對該字段對應的數據建立索引
(那有人會問不建索引還保存這個Field做什麼?
可能是隻想通過其他字段找到該字段對應的Document的時候可以
通過Document的get("Field的name")來得到這個字段的數據(當然如果也是"不存儲"方法那麼得到的將是空Null)
------個人理解)
正因爲沒有對該字段建立索引,所以搜索通過該字段建立的搜索條件的話就搜索不到內容了!
也就是說"是否索引"的意思就是是否對該Field字段中的內容建立索引

2.2.1.3 是否存儲:表示在該Field中的數據是否要被原封不動的保存在索引當中!

**注意:從搜索的角度說,因爲返回的搜索結果是Docuent對象,Document中的內容是索引中的信息,
所以Document中的Field就是剛開始建立索引時向Field中加入的數據,所以對於"不存儲"的Field,
當從返回的Document中取出對應"不存儲"字段的數據的時候將會是空的!
即不存儲的,得到搜索結果後,通過Document.get("不存儲處理方式的Field字段名");結果爲Null

2.2.2 Lucene提供了4中靜態方法可以構造Lucene定製好處理方式的Field,
也就是說用這些方法創建出來的Field就已經具有了上邊規定的3種處理方式規則!

2.2.2.1 Text方法:通過該方法創建出來的Field字段對象中的內容將會被"切詞"和
"索引(即可以通過對該Field字段建立搜索,搜索到對應的Document對象)",而是否"存儲"要看調用的是那個Text方法。
Text(String,String)和Text(String,Reader)
調用的是前邊的Text方法的話創建的Field對象中的內容將會被"存儲"到索引,
調用的是後者的Text方法的話創建的Field對象中的內容將不會被"存儲"到索引中,
因爲這種傳的是一個Reader流對象,對應的內容可能會很多,如果確實想存儲內容,
那麼要先從Reader中讀取出內容,然後放入一個String類型的對象中,然後相當於調用的前一種Text方法來創建Field對象

2.2.2.2 Keyword方法:通過該方法創建的Field對象中的內容"不會被切詞",但是會被"索引"和原封不動的"存儲"到索引中,
通過該方法創建的Field適合存儲URL地址、文件路徑、時間日期、身份證號、姓名等數據量不大的信息。

2.2.2.3 UnIndexed方法:該方法創建的Field對象中的內容"不會被切詞"和"不會被索引",只會被"存儲"到索引中。
這種Field適合保存那些比較次要的和不經常作爲關鍵字進行搜索,但是還需要和搜索到的結果一起顯示在界面上的信息。

2.2.2.4 UnStored方法:和UnIndexed方法創建的Field正相反,創建的Field中的內容會被"切詞"和"索引",但是不會被"存儲"。
這類似與調用Text(String, Reader)方法的效果。適用場合也比較相似!

**注意: 以上這四種方法如果沒有一個能夠創建出滿足我們需求的Field對象,那麼我們可以自己通過new Field()的手段,
傳遞能夠達到我們需求的那些參數來構建滿足我們需求的Field對象。

**注意: 調用構造方法的時候一般我們會調用5個參數的構造方法,而不會調用6個參數包括storeTermVector參數的構造方法,
調用5個參數的方法的時候內部會調用6個參數的併爲storeTermVector傳遞false值,這個值對於我們一般的查詢不會有太大用處,
她是有關模糊查詢的設置,所以可以不理會它的存在,所以建議直接調用5個參數的構造方法,即實現爲storeTermVector參數傳遞默認的數值。

3. 索引的添加----IndexWriter類(索引寫入器): 是Lucene中最重要的一個類,它的功能就是將文檔寫入到索引,同時控制寫入索引過程中的各種參數。

3.1 初始化:IndexWriter的初始化並不複雜,它提供了很多重載的構造方法。所以構造一個IndexWriter對象比較容易。
通常我們使用3個參數的構造方法來構造IndexWriter對象:下面介紹3個具體的參數是什麼

3.1.1 索引文件存儲的目標路徑:IndexWriter需要知道把索引創建到什麼位置。
這個參數可以是一個String類型指定的路徑字符串;
也可以是一個用File對象封裝的路徑地址;
也可以是Lucene提供的Directory類(後邊將會詳細講解)

3.1.2 分析器:也可叫分詞器,對要創建索引的文檔內容進行分析切詞,分析器類繼承自org.apache.lucene.analysis.Analyzer類
就是在IndexWriter將文檔內容寫入索引之前,先通過分析器對文檔內容進行分析切詞,然後再寫入索引

3.1.3 是否重新創建索引:IndexWriter創建索引之前還要檢查一下是要重新創建索引還是進行增量添加索引。
就是當傳入true的時候,IndexWriter向指定的保存索引的目錄中保存索引之前,
不管目錄中是否有已經有索引文件,一律全部清空,然後在保存索引文件;
當傳入false的時候,那麼IndexWriter保存索引的時候會在原來的基礎上增量添加新的索引文件。
(注意:
這裏說的增量不太明確,可能有兩種意思,
一是在原文件的基礎上在原文件內部添加新內容(經驗證是這種方式);二是對新添加的索引文件建立一個新索引文件)

**注意:通常情況下,在一段代碼中要把文檔索引寫入某個目錄時,只能聲明一個IndexWriter的對象來進行操作,
因爲如果使用多個IndexWriter對象向同一個目錄中寫入索引時會引發很多的線程同步問題,後面將會具體講解。

3.2 向目錄中寫入索引:就是將Document對象保存成索引文件。
方法就是調用IndexWriter對象的addDocument()方法,將Document保存到IndexWriter對象綁定好的存放索引文件的目錄。

注意:調用完addDocument()方法之後一定要記住關閉IndexWriter索引寫入器,即調用IndexWriter的close()方法,
如果不調用的話,雖然調用了addDocument()向目錄寫入索引文件,但是它並不會真正的立即將索引文件寫入到目錄磁盤中,
但是它確實是已經將Document對象生成了索引文件,只不過此時IndexWriter將生成好的索引文件保存在了內存中,
只有調用了IndexWriter的close方法之後,IndexWriter纔會將內存中的索引文件真正的寫入到目錄磁盤中同時會關閉流對象。
如果不關閉IndexWriter對象的話,那麼內存中已經生成的索引文件會因爲沒有調用哪個close方法而丟失。

3.3 使用IndexWriter對象調整控制性能參數:目的就是IndexWriter創建索引的同時還可以對一些創建索引過程中的參數進行設置與修改。
設置這些參數可以提升IndexWriter對象創建索引的性能。

3.3.1 mergeFactor參數:它用於控制Lucene在把索引從內存寫入磁盤上的文件系統時內存中最大的Docunent數量,
同時還控制內存中最大的Segment數量。

例如:假設mergeFactor設置爲10(默認值),當IndexWriter爲第11個Document要建立索引到內存中時,它會建立一個Segment文件,
該文件包含了10個Document生成的索引文件,就這樣一次一次運行,當Segment在磁盤中的數量達到10個的時候,
這10個Segment將會合併成一個Segment文件,此時按照開始10個Document生成的索引文件保存到一個Segment的規律,
那麼此時的Segment文件中有100個Document生成的索引文件。就這樣依次來推!

注意:我們可能對Segment不太瞭解,Segment是Lucene索引文件中最大的單位,
從上面的合併原則也可以看出,具體解析見後邊。

3.3.2 maxMergeDocs參數:在mergeFactor中提到,當內存中的索引文件達到一定的數量,
就會合併成Segment文件並寫到磁盤中,當Segment達到一定數量也會合並Segment文件。但是如果要建立索引的文檔非常多的話,
到最後一個Segment文件的容量可能會非常的龐大,這樣會很影響搜索的效率。、
因此Lucene提供了這個參數maxMergeDocs用來限制Segment文檔中能容納索引文件(Document生成的索引文件)的數量。

3.3.3 minMergeDocs參數:用於控制內存中持有的Document(或說成Document生成的索引文件)的數量的,
也就是說當達到這個數量的時候,內存中的文檔都要刷新到(寫入到)磁盤中保存。

3.4 限制Field的長度:在IndexWriter中提供了一個maxFieldLength屬性參數來限定Field中保存的數據的長度,默認爲10000.
就是說當我們採用默認值的時候,如果Field中保存的數據大小是10001個分詞(這裏的單位是分詞,而不是字符長度或其他單位),
就是比如有一段話"我們是中國人",那麼它保存到一個Field對象中的長度是2,因爲分詞後這段話就有兩個分詞"我們"和"中國人",
那麼對該字段中數據創建索引的時候,就只會爲前10000個分詞創建索引,10000之後的就丟失了。

** Term的概念:個人理解應該是分詞的封裝類。就是在創建索引之前我先要對數據進行分析切詞,
分成的每個詞就是封裝到Term中來進行保存。如“我們是中國人"分詞後爲"我們"和"中國人",
那麼在Lucene中這兩個分詞的保存形式是以Term對象的形式存在的,就是把"我們"封裝成一個Term對象,
把"中國人"也封裝成一個Term對象------個人理解。

4.Lucene的索引文件格式簡述:Lucene的索引文件有其固定的格式,這裏我們列出一些重要的內容進行說明。

4.1 Segment(段):Segment是Lucene索引文件的一個最基本單位
一個Segment是一個獨立的索引,可以通過IndexSearch進行單獨查詢搜索。
Lucene的工作其實就是不斷的向磁盤中加入新的Segment段,然後再按照一定的算法合併Segment段以創建一個新的Segment段。

**注意:打開保存索引文件的目錄我們會看到,目錄中有一個且只能有一個segments文件,
該文件中保存了當前segments文件所在目錄中相關的段的信息,比如有多少個Segment段、每個Segment段有多大等信息。

**注意:每個Segment的所有相關文件的前綴名都是一樣的,如下表指定的目錄中只有一個Segment段,
所以相關的文件名前都有相同的前綴"_1"。

例如:目錄表:
_1,f1
_1,f2
_1,f3
_1,fdk
_1,fdx
_1,fnm
_1,frq
_1,prx
_1,tii
_1,tis
deletable
segments

4.2 索引文件:上邊的目錄中有很多不同後綴名的文件,我們一一認識一下:

.fnm :主要包括了各個域(字段)的名稱信息(Field Name)
.fdx :主要包括了各個域(字段)的索引信息(Field Index)
.fdt :主要包括了各個域(字段)的內容信息(Field Data)
.tis :主要包括了詞條(分詞)的各種信息,有詞條數量、間隔等(Term Info)
.tii :主要包括了詞條的各個信息的索引(Term Info Index)
.frq :主要包括了詞條的頻率信息(Frequency)
.prx :主要包括了詞條在文檔中的位置信息(Position)
.f :主要包括了各個詞條或文檔的分值情況,用於對文檔進行排序
deletable :主要包括了要刪除的文檔信息

4.3 複合索引格式:向上面那種索引文件的存儲有一定的缺陷,
就是當創建的索引文件數量非常巨大的時候,系統要同時打開多個文件
(進行檢索搜索或進行其他操作---個人理解,因爲沒有給出明確說明,所以自己理解),
這樣對系統的資源來說可能是一種浪費,所以Lucene還提供了一種"複合索引"的格式。

例如:一個複合索引的目錄
_1.cfs
deletable
segments

** 說明:在IndexWriter中有一個方法setUseCompoundFile(boolean),用來設置是否使用複合索引格式,默認是使用。

** 區別;與之前不同的地方就是在一個複合索引文件中每一個Segment段都有含有一個單獨的.cfs文件

4.4 索引轉換:如果開發者想要在"複合索引文件格式"和"多文件索引文件格式"之間相互轉換需要一下步驟

4.4.1 生成一個IndexWriter對象打開索引

4.4.2 指定生成複合索引的類型

4.4.3 優化索引

4.4.4 關閉索引

**注意:這裏要特別注意的是,一定要在構造IndexWriter對象時爲IndexWriter類的構造方法傳入"不重新創建索引文件",
即最後一個參數傳入false值。

4.5 索引的存放位置------FSDirectory和RAMDirectory類:
Lucene提供了兩種存放索引的位置,一種是磁盤(FSDirectory類)、一種是內存(RAMDirectory類);

問:我們會問之前我們也沒有使用這兩種方式怎麼還能把索引文件保存到磁盤中呢?

答:那是因爲之前我們使用IndexWriter(String, Analyzer, boolean)的時候雖然傳入的是String類型的目錄結構,
但是Lucene內部使用了FSDirectory類對象對String路徑字符串進行了封裝,所以才能將索引文件保存到磁盤當中。

4.5.1 在磁盤中存放索引文件------FSDirectory類:是File System Directory的縮寫。
通常在使用FSDirectory的時候,Lucene會自動在內存中建立緩存,等到一定的程度就把索引寫入磁盤
(就是我們前面提到的只有通過Document生成的索引信息達到一定量之後纔會刷新到磁盤)。
當然這一步操作對用戶來講是透明的,因爲用戶看不見內存中的操作,只能看到寫到磁盤中的索引文件的內容。

例子:
//創建第一個Document對象
Document doc1 = new Document();
doc1.add(Field.Text("name","word1 word2 word3"));
//創建第二個Document對象
Document doc2 = new Document();
doc2.add(Field.Text("name","word1 word2 word3"));
/*
創建索引寫入器對象,此處傳入的是FSDirectory對象
通過FSDirectory.getDirectory("", true)來創建
第一個參數是保存索引的路徑
第二個參數是"是否要重新創建索引"和IndexWriter構造方法的最後一個參數一樣!
*/
IndexWriter writer = new IndexWriter(FSDirectory.getDirectory("C://lucene_index", true), new StandardAnalyzer(), true);
//設置最大域長度爲3個分詞(Term)大小(注意這個域大小的設定主要是爲下邊要添加的這一個Field設定的----個人理解)
writer.maxFieldLength = 3;
writer.addDocument(doc1);
//設置最大域長度爲3個分詞(Term)大小
writer.maxFieldLength = 3;
writer.addDocument(doc2);
//關閉,將內存中緩存中沒有寫入到磁盤的索引內容通通寫入磁盤
writer.close();

//開始進入搜索
IndexSearch search = new IndexSearch("C://lucene_index");
//接收結果集
Hits hits = null;
//封裝條件對象,"word1"爲要搜索的關鍵字,"name"是Field字段名稱
Query query = QueryParser.parse("word1", "name", new StandardAnalyzer());
//獲得結果
hits = search.search(query);
//打印結果
System.out.println("查詢word1的結果條數爲 " + hits.length());

//搜索word3關鍵字
Query query = QueryParser.parse("word3", "name", new StandardAnalyzer());
hits = search.search(query);
System.out.println("查詢word3的結果條數爲 " + hits.length());

4.5.2 在內存中存放索引文件------RAMDirectory類:從功能上講,FSDirectory類的對象在磁盤上能操作的事,
RAMDirectory類的對象在內存中都能完成,並且會有更快的速度。

缺點:就是在JVM退出後,內存中保存的索引信息數據將不復存在!

例子:
//創建兩個Document對象,同上邊的代碼
//使用RAMDirectory創建IndexWriter對象,直接通過調用缺省的構造方法創建就行了
IndexWriter writer = new IndexWriter(new RAMDirectory(), new StandardAnalyzer(), true);
//設置Field字段的大小,爲一個分詞大小,說明doc1中的Field的大小是一個分詞大小
writer.maxFieldLength = 1;
writer.addDocument(doc1);
//設置大小爲3個分詞大小,說明doc2中的Field的大小長度爲三個分詞
writer.maxFieldLength = 3;
writer.addDocument(doc2);
//關閉
writer.close();

//開始搜索,同上;但是搜索結果將不會一樣,因爲這裏的doc1中的Field長度只爲一個分詞,所以只能將word1作爲索引保存,
所以當通過搜索關鍵子word3搜索是隻能搜索到一個結果,因爲doc1中的Field中沒有word3的信息。

缺點:唯一的缺點就是內存中的索引沒有得到保存,而是隨着JVM的關閉索引信息也跟着消失了,
下一小節中我們會加入一個手段,在JVM關閉之前把內存中的索引寫入到磁盤中!

4.5.3 索引的合併: 就是創建一個FSDirectory類型的IndexWriter對象,創建一個RAMDirectory類型的IndexWriter對象,
然後向每個IndexWriter中都保存一個索引文件,即向磁盤中保存一個索引文件,向內存中也保存一個索引文件,
因爲內存中的JVM關閉後會消失,所以這裏我讓內存中的和磁盤中的索引文件合併,都合併到磁盤中,
這樣就解決了內存中索引文件丟失的問題。但是合併過程有一些細節需要我們特別注意一下!!

例子:
//創建一個磁盤Directory和一個內存Directory對象
FSDirectory fsDir = FSDirectory.getDirectory("C://lucene_index", true);
RAMDirectory ramDir = new RAMDirectory();
//分別創建對應的IndexWriter對象
IndexWriter fsWriter = new IndexWriter(fsDir, new StandardAnalyzer(), true);
IndexWriter ramWriter = new IndexWriter(ramDir, new StandardAnalyzer(), true);
//創建兩個Document文檔對象,同上

//向內存中保存索引信息
ramWriter.addDocument(doc1);
//向磁盤中保存索引信息
fsWriter.addDocument(doc2);

//索引創建完之後,爲了能夠合併不出問題要先將內存的索引寫入器關閉,確保所有的索引都寫入到了內存中
ramWriter.close();

/*
合併索引文件,傳遞參數是爲了合併,首先要讀出合併目標目錄中的所有索引,加載到內存中,
然後和現在內存中擁有的所有的索引組合起來,然後重新寫入到目標目錄,以達到合併的目的
*/
fsWriter.addIndexs(new Directory[]{fsDir);

//合併完成後就可以反比磁盤索引寫入器了
fsWriter.close();

//下邊開始搜索,同上

** 注意:這裏要特別小心的地方就是合併索引之前,一定要先關閉內存寫入器,然後在合併,
先關閉內存寫入器的目的是讓內存寫入器確保所有要保存到內存中的索引全部寫入到內存中,
這樣才能保證合併的成功,如果不這樣先關閉內存索引寫入器的話,合併的時候會報錯誤信息。

** 注意:如果是要合併兩個FSDirectory的話,方法和上邊一樣。

4.6 從索引中刪除Document(文檔):索引的維護中很重要的一部分就是刪除沒有用處的Document文檔。

**聲明:下面爲什麼不說成刪除索引中的Document文檔呢?
因爲通過Lucene寫入索引或者搜索索引的時候,通過Lucene提供的API先要將數據加載到內存才能保存索引或者搜索索引,
而恰恰在內存中索引存在的形式是以一個個Document對象存在的,所以下面我們就直接說成了刪除Document文檔,
而沒有說成刪除所以中的Document文檔。這些自己理解一下就行了!

4.6.1 刪除Document文檔的工具類----IndexReader類:
創建方法可以通過IndexReader.open()方法,傳遞索引保存的地址就可以了。
這樣IndexReader對象就可以對指定目錄中保存的索引進行操作了。
《具體方法聲明可以查看Javadoc API文檔》

4.6.2 刪除與反刪除某個特定的Document文檔:就是通過IndexReader對象的delete(int)方法,
就可以刪除指定索引值(可以叫做位置值,和for循環中遍歷的索引值類似,只不過這個值是Document保存的位置值),
這樣就可以刪除指定位置的Document文檔。

例如:
//創建兩個Document文檔,同上
......
//創建索引寫入器
IndexWriter writer = new IndexWriter("C://lucene_index", new StandardAnalyzer(), true);
/*
添加Document對象進行保存索引
** 注意:這裏存在了位置值,第一個保存添加的Document的位置值是0,之後的一次累加
*/
writer.addDocument(doc1);
writer.addDocument(doc2);
writer.close();

//創建IndexReader對象以便對索引進行操控:傳入索引保存目錄地址
IndexReader reader = IndexReader.open("C://lucene_index");
//刪除第一個保存添加的Document文檔
reader.delete(0);
//關閉流
reader.close();

//搜索:以便通過搜索結果驗證是否成功的刪除,代碼同上
......

** 缺點:通過這種delete方法刪除,只適合與知道Document文檔位置的情況,如果保存的Document數量比較大,
我想要刪除裏面的保存Field字段名爲"name",並且分詞中有"word1"的那個Document,很明顯這種方法是做不到的。

** 注意:其實delete方法並不是真正的將Document文檔信息刪除了,只是對對應的Document文檔對象的一個"是否刪除"的屬性
標記爲真,這樣就代表了該Document剛剛做了一個刪除操作,這樣在搜索的時候就找不到了,如果我們想恢復刪除,
只須調用IndexReader類的undeleteAll()方法,就可以恢復索引刪除狀態的Document文檔,
這樣這些恢復了的Document文檔在搜索的時候就可以找到了。

4.6.3 按Field字段來刪除對應的Document文檔:就是按照指定的Field字段的名稱,
和字段中保存的數據被分詞後的某個分詞進行查找,找到後刪除對應的Document文檔,
這樣對於滿足上面的需求就比較容易了。

例子:
//創建兩個Document對象,同上
......
//創建索引寫入器,並且保存兩個Document文檔到索引並且關閉,同上
......
//創建IndexReader對象
IndexReader reader = IndexReader.open("C://lucene_index");
/*
刪除包含指定分詞的Document文檔
** 注意:Term代表一個分詞,new Term()的第一個參數是字段名稱,第二個參數是哪個分詞,
最後將會找到包含有指定字段名的Field中同時也包含指定的分詞的Document文檔,此時將會把它刪除
*/
reader.delete(new Term("name", "word1"));
//關閉流
reader.close();

//搜索代碼,同上
......

** 說明:這種方式比較常用,比如刪除某一網站下的所有Web文檔,並且知道該網站的域名是www.abc.com
則可以這樣刪除,reader.delete(new Term("domain", "www.abc.com"));

4.7 Lucene的索引優化:建立完索引之後,並不是原封不動的放置在文件系統中,而是對其進行適當的優化。

4.7.1 爲什麼要優化索引:優化索引就是就是通過合併磁盤上的索引文件,以便減少文件的數量,從而也減少搜索索引的時間。
雖然可以通過控制性能參數來改變磁盤上的Segment索引文件數量。但是建立完索引文件後,
任然可能存在大量未進行合併的Segment索引文件。

又因爲在搜索的時候,主要就是和磁盤上的所有索引文件打交道,如果搜索的Segment索引文件數量過大,
那麼搜索器進行搜索的時候必然會增多同時打開的Segment索引文件,這樣如果在一個多線程的環境下,
多個用戶同時進行搜索就是同時打開大量的索引文件,這樣會大大降低檢索的效率,也會增大服務器的負載。

所以還需要額外的優化手段!!

4.7.2 優化索引的方法:使用IndexWriter類的optimize()方法,它會把磁盤上的多個Segment索引文件進行合併,
組成一個全新的Segment索引文件。

** 注意:optimize()方法並不會提升建立索引的速度,相反,它會降低建立索引的速度。
而且由於在合併索引的時候需要額外的磁盤空間來創建新的Segment索引文件,因此它對磁盤空間的要求也會增加。

** 使用原則:通常情況下,optimize()方法不應當在索引建立的過程中調用,而應當是完成建立大批量索引之後再進行調用.

** 原因:因爲在對索引優化的時候,磁盤的I/O操作頻繁,如果太過頻繁的進行索引優化,會導致系統吞吐量大幅度降低。
因此建議當你一次性要爲大量文檔建立索引時,最後再建完後再進行索引的優化。
如果是頻繁的索引增加,但是每次增加的週期比較長,則可以制定一個適合的週期進行索引的優化。

4.8 Lucene索引的同步機制(內部實現,僅供瞭解):這是一個非常重要的話題,很好的理解它們是正確使用Lucene的一個關鍵。

4.8.1 同步法則:在Lucene的操作過程中,有很多操作對都能對索引進行修改。如果在一個多線程的環境下,
那麼很容易出現同步問題,所以Lucene中有以下同步法則:這些法則是用來提醒我們開發的時候不要違背,如果違背程序會報錯。

4.8.1.1 任何數量的只讀操作可以被同時執行,比如使用IndexSearch對索引進行檢索。

4.8.1.2 任何數量的只讀操作都可以在索引正在被修改時執行,
這裏的"修改"指的是有進程正在向索引中添加新的Document文檔、刪除舊文檔或者是索引正在進行優化的過程。

4.8.1.3 在同一時間內,只可以有一個修改索引的進程對索引進行操作。
也就是說在同一時間內,只可以有一個IndexWriter或是IndexReader獲取對索引的使用權。

4.8.2 Lucene的索引"鎖":爲了保證Lucene的索引不被我操作所破壞,Lucene設置了文件鎖。
這些鎖默認情況下均放置在系統的臨時文件夾下,這個文件夾的路徑是由java中的java.io.tempdir系統變量所指定的。
Lucene中提供了兩種鎖:writer.lock和commit.lock鎖

4.8.2.1 writer.lock鎖:該鎖的主要目的是爲了防止多個線程同時修改一個索引文件而設置的。通常情況下,
它由IndexWriter在初始化時獲得,而在關閉時釋放;另外當IndexReader從索引中刪除文檔或反刪除文檔時也會獲得該鎖。

4.8.2.2 commit.lock鎖:主要是在索引的Segment被建立、合併或讀取時生成的。
它會在IndexReader讀取各個Segment信息之時被獲取,在完成讀取之時釋放。

發佈了101 篇原創文章 · 獲贊 26 · 訪問量 29萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章