Lucene學(xué)習筆記收藏1.Lucene的工作流程:
(1) 使用IndexWriter,在指定的目錄建立索引的文件
(2) 將需要檢索的數據轉換位Document的Filed對象,然后將Document用IndexWriter添加倒索引的文件中
(3) 處理索引信息,關(guān)閉IndexWriter流
(4) 創(chuàng )建搜索的Query
(5) 給IndexSearcher
2.Lucene的字段類(lèi)型
Lucene有四種不同的字段類(lèi)型:Keyword,UnIndexed,UnStored和Text,用于指定建立最佳索引。
Keyword字段是指不需要分析器解析但需要被編入索引并保存到索引中的部分。JavaSourceCodeIndexer類(lèi)使用該字段來(lái)保存導入類(lèi)的聲明。
UnIndexed字段是既不被分析也不被索引,但是要被逐字逐句的將其值保存到索引中。由于我們一般要存儲文件的位置但又很少用文件名作為關(guān)鍵字來(lái)搜索,所以用該字段來(lái)索引Java文件名。
UnStored字段和UnIndexed字段相反。該類(lèi)型的Field要被分析并編入索引,但其值不會(huì )被保存到索引中。由于存儲方法的全部源代碼需要大量的空間。所以用UnStored字段來(lái)存儲被索引的方法源代碼??梢灾苯訌腏ava源文件中取出方法的源代碼,這樣作可以控制我們的索引的大小。
Text字段在索引過(guò)程中是要被分析、索引并保存的。類(lèi)名是作為T(mén)ext字段來(lái)保存。下表展示了JavaSourceCodeIndexer類(lèi)使用Field字段的一般情況。
3.基本概念(與傳統表的對比):
Lucene
傳統表
說(shuō)明
IndexWriter
table
Document
一條記錄
Field
每個(gè)字段
分為可被索引的,可切分的,不可被切分的,不可被索引的幾種組合類(lèi)型
Hits
RecoreSet
結果集
IndexWriter提供了一些參數可供設置,列表如下
屬性
默認值
說(shuō)明
mergeFactor
org.apache.lucene.mergeFactor
10
控制index的大小和頻率,兩個(gè)作用
1.一個(gè)段有多少document
2.多少個(gè)段合成一個(gè)大段
maxMergeDocs
org.apache.lucene.maxMergeDocs
Integer.MAX_VALUE
限制一個(gè)段中的document數目
minMergeDocs
org.apache.lucene.minMergeDocs
10
緩存在內存中的document數目,超過(guò)他以后會(huì )寫(xiě)入到磁盤(pán)
maxFieldLength
1000
一個(gè)Field中最大Term數目,超過(guò)部分忽略,不會(huì )index到field中,所以自然也就搜索不到
這些參數的的詳細說(shuō)明比較復雜:mergeFactor有雙重作用
(1)設置每mergeFactor個(gè)document寫(xiě)入一個(gè)段,比如每10個(gè)document寫(xiě)入一個(gè)段
(2)設置每mergeFacotr個(gè)小段合并到一個(gè)大段,比如10個(gè)document的時(shí)候合并為1小段,以后有10個(gè)小段以后合并到一個(gè)大段,有10個(gè)大段以后再合并,實(shí)際的document數目會(huì )是mergeFactor的指數
簡(jiǎn)單的來(lái)說(shuō)mergeFactor 越大,系統會(huì )用更多的內存,更少磁盤(pán)處理,如果要打批量的作index,那么把mergeFactor設置大沒(méi)錯, mergeFactor 小了以后, index數目也會(huì )增多,searhing的效率會(huì )降低,但是mergeFactor增大一點(diǎn)一點(diǎn),內存消耗會(huì )增大很多(指數關(guān)系),所以要留意不要”out of memory”
把maxMergeDocs設置小,可以強制讓達到一定數量的document寫(xiě)為一個(gè)段,這樣可以抵消部分mergeFactor的作用.
minMergeDocs相當于設置一個(gè)小的cache,第一個(gè)這個(gè)數目的document會(huì )留在內存里面,不寫(xiě)入磁盤(pán)。這些參數同樣是沒(méi)有最佳值的,必須根據實(shí)際情況一點(diǎn)點(diǎn)調整。
maxFieldLength可以在任何時(shí)刻設置,設置后,接下來(lái)的index的Field會(huì )按照新的length截取,之前已經(jīng)index的部分不會(huì )改變??梢栽O置為Integer.MAX_VALUE
4.幾種查詢(xún)方式
查詢(xún)方式
說(shuō)明
TermQuery
條件查詢(xún)
例如:TermQuery tquery=new TermQuery(new Term("name","jerry"));
name:字段名
jerry:要搜索的字符串
MultiTermQuery
多個(gè)字段進(jìn)行同一關(guān)鍵字的查詢(xún)
Query query= null;
Query =MultiFieldQueryParser.parse("我",new String[]
{"title","content"},analyzer);
Searcher searcher=new IndexSearcher(indexFilePath);
Hits hits=searcher.search(query);
BooleanQuery
例如:BooleanQuery bquery=new BooleanQuery();
bquery.add(query,true,false);
bquery.add(mquery,true,false);
bquery.add(tquery,true,false);
Searcher searcher=new IndexSearcher(indexFilePath);
Hits hits=searcher.search(bquery);
WildcardQuery
語(yǔ)義查詢(xún)(通配符查詢(xún))
例:Query query= new WildcardQuery(new Term("sender","*davy*"));
PhraseQuery
短語(yǔ)查詢(xún)
PrefixQuery
前綴查詢(xún)
PhrasePrefixQuery
短語(yǔ)前綴查詢(xún)
FuzzyQuery
模糊查詢(xún)
RangeQuery
范圍查詢(xún)
SpanQuery
范圍查詢(xún)
在全文檢索時(shí)建議大家先采用語(yǔ)義時(shí)的搜索,先搜索出有意義的內容,之后再進(jìn)行模糊之類(lèi)的搜索
(1)聯(lián)合兩個(gè)索引查詢(xún),已解決:
IndexSearcher[] searchers = new IndexSearcher[2];
searchers[0] = new IndexSearcher(m_indexpath);
searchers[1] = new IndexSearcher(m_outindexpath);
MultiSearcher multiSearcher = new MultiSearcher(searchers);
(2)還有個(gè)進(jìn)行多條件搜索 and 與 or 的操作————
用 MultiFieldQueryParser
建議重新封裝
MultiFieldQueryParser.Parser(p[],d[],f[],analyer) 成or 與 and操作合一
或者
BooleanQuery m_BooleanQuery = new BooleanQuery();
Query query = QueryParser.Parse(m_SearchText, "INSTRUMENT_NAME", analyzer);
Query query2 = QueryParser.Parse(m_SearchText2, "INSTRUMENT_NAME2", analyzer);
m_BooleanQuery.Add(query, true, false);
m_BooleanQuery.Add(query2, true, false);
(3)復合查詢(xún)(多種查詢(xún)條件的綜合查詢(xún))
Query query=MultiFieldQueryParser.parse("索引”,new String[]
{"title","content"},analyzer);
Searcher searcher=new IndexSearcher(indexFilePath);
Hits hits=searcher.search(query);
for (int i = 0; i < hits.length(); i++)
{
System.out.println(hits.doc(i).get("name"));
}
5.為查詢(xún)優(yōu)化索引(index)
Indexwriter.optimize()方法可以為查詢(xún)優(yōu)化索引(index),之前提到的參數調優(yōu)是為indexing過(guò)程本身優(yōu)化,而這里是為查詢(xún)優(yōu)化,優(yōu)化主要是減少index文件數,這樣讓查詢(xún)的時(shí)候少打開(kāi)文件,優(yōu)化過(guò)程中,lucene會(huì )拷貝舊的index再合并,合并完成以后刪除舊的index,所以在此期間,磁盤(pán)占用增加, IO符合也會(huì )增加,在優(yōu)化完成瞬間,磁盤(pán)占用會(huì )是優(yōu)化前的2倍,在optimize過(guò)程中可以同時(shí)作search。
4.org.apache.lucene.document.Field
即上文所說(shuō)的“字段”,它是Document的片段section。
Field的構造函數:
Field(String name, String string, boolean store, boolean index, boolean token)。
Indexed:如果字段是Indexed的,表示這個(gè)字段是可檢索的。
Stored:如果字段是Stored的,表示這個(gè)字段的值可以從檢索結果中得到。
Tokenized:如果一個(gè)字段是Tokenized的,表示它是有經(jīng)過(guò)Analyzer轉變后成為一個(gè)tokens序列,在這個(gè)轉變過(guò)程tokenization中, Analyzer提取出需要進(jìn)行索引的文本,而剔除一些冗余的詞句(例如:a,the,they等,詳見(jiàn) org.apache.lucene.analysis.StopAnalyzer.ENGLISH_STOP_WORDS和 org.apache.lucene.analysis.standard.StandardAnalyzer(String[] stopWords)的API)。Token是索引時(shí)候的.
類(lèi)型
Analyzed
Indexed
Stored
說(shuō)明
Field.Keyword(String,String/Date)
N
Y
Y
這個(gè)Field用來(lái)儲存會(huì )直接用來(lái)檢索的比如(編號,姓名,日期等)
Field.UnIndexed(String,String)
N
N
Y
不會(huì )用來(lái)檢索的信息,但是檢索后需要顯示的,比如,硬件序列號,文檔的url地址
Field.UnStored(String,String)
Y
Y
N
大段文本內容,會(huì )用來(lái)檢索,但是檢索后不需要從index中取內容,可以根據url去load真實(shí)的內容
Field.Text(String,String)
Y
Y
Y
檢索,獲取都需要的內容,直接放index中,不過(guò)這樣會(huì )增大index
Field.Text(String,Reader)
Y
Y
N
如果是一個(gè)Reader, lucene猜測內容比較多,會(huì )采用Unstored的策略.
5.Lucene 的檢索結果排序
Lucene的排序主要是對org.apache.lucene.search.Sort的使用。Sort可以直接根據字段Field生成,也可以根據標準的SortField生成,但是作為Sort的字段,必須符合以下的條件:唯一值以及Indexed??梢詫ntegers, Floats, Strings三種類(lèi)型排序。
對整數型的ID檢索結果排序只要進(jìn)行以下的簡(jiǎn)單操作:
Sort sort = new Sort("id");
Hits hits = searcher.search(query, sort);
用戶(hù)還可以根據自己定義更加復雜的排序,詳細請參考API。
6.分析器
Lucene使用分析器來(lái)處理被索引的文本。在將其存入索引之前,分析器用于將文本標記化、摘錄有關(guān)的單詞、丟棄共有的單詞、處理派生詞(把派生詞還原到詞根形式,意思是把bowling、bowler和bowls還原為bowl)和完成其它要做的處理。Lucene提供的通用分析器是:
SimpleAnalyzer:用字符串標記一組單詞并且轉化為小寫(xiě)字母。
StandardAnalyzer:用字符串標記一組單詞,可識別縮寫(xiě)詞、email地址、主機名稱(chēng)等等。并丟棄基于英語(yǔ)的stop words (a, an, the, to)等、處理派生詞。
ChineseAnalyzer.class,它是一個(gè)單字分析法,它把句子中的詞全部分成一個(gè)一個(gè)的字符,以單個(gè)字為單位存儲。
CJKAnalyzer.class,它是雙字分析法,它把中文以雙字為單位拆分得到結果,從而建立詞條。當然這些得到的雙字詞中會(huì )有很多不符合中文語(yǔ)義單位的雙字被送進(jìn)索引。
十、需要注意的問(wèn)題:
1 .IndexWriter在添加新的document后,需要重新建立Index,則需要調用writer.optimize();方法
2. Lucene沒(méi)有update索引的方法,需要刪除后重新建立,參考remove方法
3 .用IndexReader刪除Document后,需要重新用IndexWriter進(jìn)行整理,否則無(wú)法在進(jìn)行搜索(不知道是不是我設置問(wèn)題)
4.Lucene先在內存中進(jìn)行索引操作,并根據一定的批量進(jìn)行文件的寫(xiě)入。這個(gè)批次的間隔越大,文件的寫(xiě)入次數越少,但占用內存會(huì )很多。反之占用內存少,但文件IO操作頻繁,索引速度會(huì )很慢。在IndexWriter中有一個(gè)MERGE_FACTOR參數可以幫助你在構造索引器后根據應用環(huán)境的情況充分利用內存減少文件的操作。根據我的使用經(jīng)驗:缺省Indexer是每20條記錄索引后寫(xiě)入一次,每將MERGE_FACTOR增加50倍,索引速度可以提高1倍左右。
5.并發(fā)操作Lucene
(1)所有只讀操作都可以并發(fā)
(2)在index被修改期間,所有只讀操作都可以并發(fā)
(3)對index修改操作不能并發(fā),一個(gè)index只能被一個(gè)線(xiàn)程占用
(4)ndex的優(yōu)化,合并,添加都是修改操作
(5)但需要注意的是,在創(chuàng )建搜索的時(shí)候用:
searcher = new IndexSearcher(IndexReader.open("E:\\lucene\\test4\\index"));
searcher.close();
這時(shí)候是不能關(guān)閉searcher的.
如果想讓searcher能關(guān)閉,就不要用IndexReader了:
searcher = new IndexSearcher("E:\\lucene\\test4\\index");
6.Locking機制
lucence內部使用文件來(lái)locking,默認的locking文件放在java.io.tmpdir,可以通過(guò)-Dorg.apache.lucene.lockDir=xxx指定新的dir,有write.lock commit.lock兩個(gè)文件,lock文件用來(lái)防止并行操作index,如果并行操作, lucene會(huì )拋出異常,可以通過(guò)設置-DdisableLuceneLocks=true來(lái)禁止locking,這樣做一般來(lái)說(shuō)很危險,除非你有操作系統或者物理級別的只讀保證,比如把index文件刻盤(pán)到CDROM上。
十一、2.0中新增特性
1.新增類(lèi): org.apache.lucene.index.IndexModifier ,它合并了 IndexWriter 和 IndexReader,好處是我們可以增加和刪除文檔的時(shí)候不同擔心 synchronisation/locking 的問(wèn)題了。
2.增加對 contrib/highlighter 的 NullFragmenter , 這對全文本加亮很有用。
3.增加了新類(lèi) MatchAllDocsQuery 用來(lái)匹配所有文檔。
4.. 增加 ParallelReader,這個(gè)一種IndexReader 他合并多個(gè)單獨的索引到一個(gè)單獨的虛擬索引上。
5.增加 Hits.iterator() 方法和相應的 HitIterator 和 Hit 對象。
他提供了對 Hits對象標準的 java.util.Iterator 疊代操作。
每個(gè)iterator‘s next() 方法返回一個(gè) Hit 對象。
6. 在 term vectors 中增加了位置和偏移信息。(Grant Ingersoll & Christoph)
7. 增加了一個(gè)新的 DateTools 。允許用戶(hù)格式化日期到一種更可讀的格式,以便于更好的適應索引。DateTools 不像 DateFields 類(lèi),它允許日期指定到1970年以前,但必須使用指定的日期格式。這樣,在RangeQuerys中使用就更加有效率了。
8. 增加了對壓縮字段存儲的支持。(patch #29370)
實(shí)例:
1.判斷索引文件是否存在:
/**
* 檢查索引是否存在.
* @param indexDir
* @return
*/
public static boolean indexExist(String indexDir)
{
return IndexReader.indexExists(indexDir);
}
private IndexWriter getWriter(String indexFilePath) throws Exception
{
boolean append=true;
File file=new File(indexFilePath+File.separator+"segments");
if(file.exists())
append=false;
return new IndexWriter(indexFilePath,analyzer,append);
}
2.刪除索引
/**
* 刪除索引.
* @param aTerm 索引刪除條件
* @param indexDir 索引目錄
*/
public static void deleteIndex(Term aTerm, String indexDir)
{
List aList = new ArrayList();
aList.add(aTerm);
deleteIndex(aList, indexDir);
}
/**
* 刪除索引.
* @param aTerm 索引刪除條件.
* @param indexDir 索引目錄 *
*/
public static void deleteIndex(List terms, String indexDir)
{
if (null == terms) {
return;
}
if(!indexExist(indexDir)) { return; }
IndexReader reader = null;
try {
reader = IndexReader.open(indexDir);
for (int i = 0; i < terms.size(); i++){
Term aTerm = (Term) terms.get(i);
if (null != aTerm){
reader.delete(aTerm);
}
}
} catch (IOException e){
LogMan.warn("Error in Delete Index", e);
} finally {
try{
if (null != reader){
reader.close();
}
}catch (IOException e){
LogMan.warn("Close reader Error");
}
}
}
刪除索引需要一個(gè)條件,類(lèi)似數據庫中的字段條件,例如刪除一條新聞的代碼如下:
public static void deleteNewsInfoIndex(int nid)
{
Term aTerm = new Term("nid", String.valueOf(nid));
deleteIndex(aTerm,indexDir);
}
注:本文有些知識是1.4下的,如果你用的是2.0可能這些例子不能很好的運行。不過(guò)我覺(jué)得看了以上的東西,再結合一些例子就能對lucene有一定的理解了,最起碼可以開(kāi)始干活了。在2.0版本中創(chuàng )建索引和進(jìn)行多種搜索的例子我會(huì )陸繼寫(xiě)出來(lái)與大家一起學(xué)習。