Lucene筆記+PaodingAnalyzer+高亮顯示

Lucene筆記+PaodingAnalyzer+高亮顯示 [轉]
作者:王美鳳 類別:生活理財 查看次數:1482

[1]
(1)
Lucene是一個基於Java全文搜索引擎,利用它可以輕易地爲Java軟件加入全文搜尋功能。
(2)
Lucene能做什麼?
Lucene可以對任何的數據做索引和搜索。 Lucene不管數據源是什麼格式,只要它能被轉化爲文字的形式,就可以被Lucene所分析利用。也就是說不管是MS word, Html ,pdf還是其他什麼形式的文件只要你可以從中抽取出文字形式的內容就可以被Lucene所用。
(3)
Lucene實現全文檢索功能主要有三個步驟
1、 建立索引
建立索引是全文搜索的基礎,lucene根據索引搜索用戶需要查找的目標文檔,如果沒有索引也就無所謂搜索。
2、 查找索引
當索引建立好後,就可以對其進行查找了。Lucene所建立的索引是存放在文件系統中的.
3、 更新索引
由於我們要查找的內容總是不斷變化的,所以索引文件也不是一成不變的,而是一個不斷更新的過程。Lucene也提供了豐富的功能來支持索引的更新功能。
[2]
Lucene幾個重要概念:
(1)
Document:一個要進行索引的單元,相當於數據庫的一行紀錄,任何想要被索引的數據,都必須轉化爲
Document對象存放。
(2)
Field:Document中的一個字段,相當於數據庫中的Column。
IndexWriter:負責將Document寫入索引文件。通常情況下,IndexWriter的構造函數包括了以下3個參數
:索引存放的路徑,分析器和是否重新創建索引。特別注意的一點,當IndexWriter執行完addDocument方法後,一定要記得調用自身的close方法來關閉它。只有在調用了close方法後,索引器纔會將存放在內在中的所有內容寫入磁盤並關閉輸出流。
Analyzer:分析器,主要用於文本分詞。常用的有StandardAnalyzer分析器,StopAnalyzer分析器,
WhitespaceAnalyzer分析器等。
Field對象
從Lucene的源代碼中,可以看出Field 典型構造函數如下:
Field(String name, String value, Field.Store store, Field.Index index)
其中:
Field.Index有四種屬性,分別是:
Field.Index.TOKENIZED:分詞索引
Field.Index.UN_TOKENIZED:不分詞進行索引,如作者名,日期等,不再需要分。
Field.Index.NO:不進行索引,存放不能被搜索的內容如文檔的一些附加屬性如文檔類型, URL等。
Field.Index.NO_NORMS:不分詞,建索引.但是Field的值不像通常那樣被保存,而是隻取一個byte,這樣節約存儲空間。
Field.Store也有三個屬性,分別是:
Field.Store.YES:索引文件本來只存儲索引數據, 此設計將原文內容直接也存儲在索引文件中,如文檔的標題。
Field.Store.NO:原文不存儲在索引文件中,搜索結果命中後,再根據其他附加屬性如文件的Path,數據庫的主鍵等,重新連接打開原文,適合原文內容較大的情況。
Field.Store.COMPRESS 壓縮存儲。
(3)
Directory:索引存放的位置。Lucene提供了兩種索引存放的位置,一種是磁盤,一種是內存。一般情況將索引放在磁盤上;相應地Lucene提供了FSDirectory和RAMDirectory兩個類。
(4)
Segment:段,是Lucene索引文件的最基本的一個單位。Lucene說到底就是不斷加入新的Segment,然後按一定的規則算法合併不同的Segment以合成新的Segment。
[3]
用lucene作搜索,主要步驟:
(1)建立索引:
1.創建IndexWriter.
2.創建Document,將各個document放入writer中.
3.對於數據庫查詢,先將要查詢的內容,從數據庫中獲取出來.用rs進行循環放入到doc中.
同時應用增量索引:用一個文本記錄當前已做索引的最後一個ID,下次做索引,從此文件獲取ID,從它的
下一個ID進行做索引追加到原來的索引文件中.而不是重頭做索引.
(2)進行搜索:
1.new IndexSearcher(索引目錄)=searcher
2.添加各種查詢器,生成query.
3.searcher.search(query);
4.返回Hits,用hits.length(),hits.doc(i)來輸出doc,再用doc.get("field名")輸出.
(3)
進行多域搜索:
0.new IndexSearcher(索引目錄)創建多個要查詢的索引文件,再放於數組中,最近放於MultiSearcher
1.建立field[],occurs[]
2.建立多域查詢器:MultiFieldQueryParser
3.searcher.search(query);返回Hits
4.用Hits.doc(n);可以遍歷出Document
(4)
搜索數據庫:
1.可輕在根目錄下建立一個index的目錄,用來存放index.
2.對數據表某表進行查詢,將查詢出來的字段結果-->放於document中-->通過indexwriter放入index目錄中.
3.查詢時,同上.
4.在JSP頁面,導入lucene類.可以設置一個field,填寫查詢條件,提交到hits=searcher.search()中,將
hits結果,放於中顯示出來.
(5)
增量查詢,方法基礎同上.只是在創建index時,不同:
1.增加多一個文本文件,來存放建立索引的數據表最後的ID,對數據表進行查詢時,在SQL中添加 where id > **,則是採用增量索引.而不是重頭再來.

[4]
實例
(1)數據庫索引實例:
索引輔助類:
public class JLuceneUtils {

public static Document createConfigDoc(String id,String confContext)
{
Document doc=new Document();
doc.add(new Field("id",id,Field.Store.YES,Field.Index.TOKENIZED));
doc.add(new Field("confContext",confContext,Field.Store.YES,Field.Index.TOKENIZED));
return doc;
}

public static String getStoreId(String idPath)
{
String storeId=null;
try {
File file = new File(idPath);
if (!file.exists())
file.createNewFile();
FileReader reader=new FileReader(file);
BufferedReader br=new BufferedReader(reader);
storeId=br.readLine();
if(storeId==null||storeId=="")
storeId="0";
br.close();
reader.close();
} catch (Exception e) {
e.printStackTrace();
}
return storeId;
}

public static boolean writeStoreId(String idPath,String id )
{
boolean b = false;
try {
File file = new File(idPath);
if (!file.exists()) {
file.createNewFile();
}
FileWriter fw = new FileWriter(idPath);
PrintWriter out = new PrintWriter(fw);
out.write(id);
out.close();
fw.close();
b=true;
} catch (IOException e) {
e.printStackTrace();
}
return b;
}

public static Hits luceneSearch(String indexPath,String searchMess)
{
Hits rs=null;
try {
IndexSearcher searcher = new IndexSearcher(indexPath);
QueryParser parser = new QueryParser("id",new StandardAnalyzer());
Query query=parser.parse(searchMess);
rs=searcher.search(query);
} catch (Exception e) {
e.printStackTrace();
}
return rs;
}
}
索引方法:
public String luceneSearch()
{
String idPath="D:/tomcat_bbs/webapps/BBSCS_8_0_3/storeId.txt";
String indexPath="D:/tomcat_bbs/webapps/BBSCS_8_0_3/index";
try {
File file = new File(indexPath);
if (!file.exists()) {
//for iBatis
List bbsConfigs = (List) sqlMapClientTemplate.queryForList("getAllBBSConfig");
IndexWriter writer = new IndexWriter(indexPath,new StandardAnalyzer(), true);
for (int a = 0; a < bbsConfigs.size(); a++) {
BBSConfig bbsConfig = (BBSConfig) bbsConfigs.get(a);
Document doc = JLuceneUtils.createConfigDoc(bbsConfig.getId(), bbsConfig.getConfContext());
writer.addDocument(doc);
}
writer.optimize(); //優化索引文件
writer.close();
}
Hits hits = JLuceneUtils.luceneSearch(indexPath, searchMess);
System.out.println("----------hits.length():" + hits.length());
for (int a = 0; a < hits.length(); a++) {
Document doc2 = (Document) hits.doc(a);
System.out.println(searchMess + "的值是:"+ doc2.get("confContext"));
}
} catch (Exception e) {
e.printStackTrace();
}
// return "search";
return SUCCESS;
}

(2)增量索引,各種高級索引
增量查詢:
若用增量查詢,則創建索引的邏輯,應該在用戶點擊索引時,先對storeID.txt中的ID與相應數據表中的ID進行對比,若有新記錄則進行'創建索引',若無,則直接進行對現在索引文件的查詢.
IndexWriter writer = new IndexWriter(indexPath,new StandardAnalyzer(), false);
對於增量索引,創建索引與搜索不能放在一起.因爲增量索引,是追加,若放在一起,則每次進行搜索前,都會追回索引文件中已有的索引,造成重複.

IndexWriter writer = new IndexWriter(indexPath,new StandardAnalyzer(), true);
對於要索引的對象,若不是很大的話,不要用增量索引.因爲增量索引涉及到更新索引問題.若用創建新索引,即索引文件會是最新的.

對應數據表(jason_lucene):
id name info date
1 jason 中國人 20090610
2 hwj 中原人 20090612
3 hello 國人的事懷20090616
4 jaosn 大家好 20090619

輔助類,同上.
實現方法:
public String luceneSearch2()
{
try {
String idPath = "D:/tomcat_bbs/webapps/BBSCS_8_0_3/index/storeId.txt";
String indexPath = "D:/tomcat_bbs/webapps/BBSCS_8_0_3/index";
//createIdnex 考慮增量功能
String storeId = JLuceneUtils.getStoreId(idPath);
String sql = "select id, name,info,date from jason_lucene where id >"+ storeId+" order by id";
ResultSet rs2 = this.getResult(sql);
String id="0";

/*
IndexWriter writer = new IndexWriter(indexPath,new StandardAnalyzer(), false);
對於增量索引,創建索引與搜索不能放在一起.因爲增量索引,是追加,若放在一起,則每次進行搜索前,都
會追回索引文件中已有的索引,造成重複.

IndexWriter writer = new IndexWriter(indexPath,new StandardAnalyzer(), true);
對於要索引的對象,若不是很大的話,不要用增量索引.因爲增量索引涉及到更新索引問題.若用創建新索
引,即索引文件會是最新的.
*/

IndexWriter writer = new IndexWriter(indexPath,new StandardAnalyzer(), false); //區別就在此!
while(rs2.next())
{
Document doc=new Document();
doc.add(new Field("name",rs2.getString("name"),Field.Store.YES,Field.Index.TOKENIZED));
doc.add(new Field("info",rs2.getString("info"),Field.Store.YES,Field.Index.TOKENIZED));
doc.add(new Field("date",rs2.getString("date"),Field.Store.YES,Field.Index.TOKENIZED));
writer.addDocument(doc);
id=rs2.getString("id");
}
writer.optimize();
writer.close();
boolean isSuccess=JLuceneUtils.writeStoreId(idPath, id);
System.out.println("最新索引ID寫入storeId.txt文件成功:"+isSuccess);

//刪除索引:
IndexReader reader=IndexReader.open(indexPath);
Term delterm=new Term("name","hwj"); //好像不支持中文.
reader.deleteDocuments(delterm);
reader.close();
//更新索引:即是先用上面的方法刪除特定的索引,然後再對特定的索引進行添加.添加方法也同上.

//查詢:
IndexSearcher searcher = new IndexSearcher(indexPath);
//1.普通查詢:列出包含查詢關鍵字的記錄.可在關鍵字的前面添加+/-.如:
//+jason -hwj:表示一定要包含jason,一定不要包含hwj
// QueryParser parser = new QueryParser("info",new StandardAnalyzer());
// Query query=parser.parse(searchMess);
//2進行通配符查詢:用*,表示0個或多個;?,表示一個. jas*,可查詢出jaosn,jason.
Query query1=new WildcardQuery(new Term("name",searchMess));
//3模糊查詢:若查詢的關鍵字是jason ,則會查出jason,jaosn等相似的記錄
// Query query=new FuzzyQuery(new Term("name",searchMess));
//4 範圍查詢:查詢出在一個範圍內的記錄
Term term1=new Term("date","20090611");
Term term2=new Term("date","20090618");
Query query2=new RangeQuery(term1,term2,true);
//5多條件查詢:
BooleanQuery query=new BooleanQuery();
query.add(query1, BooleanClause.Occur.MUST_NOT);
query.add(query2, BooleanClause.Occur.MUST);
Hits hits=searcher.search(query);
System.out.println("符合查詢條件'"+searchMess+"'的共有:" + hits.length() + "個結果");
for (int a = 0; a < hits.length(); a++) {
Document doc2 = (Document) hits.doc(a);
System.out.println(doc2.get("name") + "的值是:"+ doc2.get("info"));
}

} catch (Exception e) {
e.printStackTrace();
}
return SUCCESS;
}

(3)多域索引
public class LuceneTest {

public static void main(String[] args) {
try {
LuceneTest luceneTest = new LuceneTest();
// 創建索引
luceneTest.index();
// 在索引所在目錄下搜索"中國 金牌"
luceneTest.search("中國 金牌");
luceneTest.search("2008 jaosn");
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("ok");
}

public void index() throws Exception {
/* 創建索引初始化,執行這些語句將創建或清空d:\\save\\目錄下所有索引 */
IndexWriter writer1 = new IndexWriter("d:\\save\\",
new StandardAnalyzer(), true);
writer1.close();

/*
* 往創建的初始化索引中添加索引內容,StandardAnalyzer表示用lucene自帶的標準分詞機制,
* false表示不覆蓋原來該目錄的索引,細心的讀者可能已經發現, 這句話和上面的那句就這個false不一樣
*/
IndexWriter writer2 = new IndexWriter("d:\\save\\",
new StandardAnalyzer(), false);
/* 創建一份文件 */
Document doc1 = new Document();
/*
* 創建一個域ArticleTitle,並往這個域裏面添加內容 "Field.Store.YES"表示域裏面的內容將被存儲到索引
* "Field.Index.TOKENIZED"表示域裏面的內容將被索引,以便用來搜索
*/
Field field1 = new Field("ArticleTitle", "北京2008年奧運會", Field.Store.YES,
Field.Index.TOKENIZED);
/* 往文件裏添加這個域 */
doc1.add(field1);
/* 同理:創建另外一個域ArticleText,並往這個域裏面添加內容 */
Field field2 = new Field("ArticleText", "這是一屆創造奇蹟、超越夢想的奧運會.......",
Field.Store.YES, Field.Index.TOKENIZED);
doc1.add(field2);
// 在這裏還可以添加其他域
/* 添加這份文件到索引 */
writer2.addDocument(doc1);

/* 同理:創建第二份文件 */
Document doc2 = new Document();
field1 = new Field("ArticleTitle", "中國獲得全球讚譽", Field.Store.YES,
Field.Index.TOKENIZED);
doc2.add(field1);
field2 = new Field("ArticleText", "中國所取得的金牌總數排行榜的榜首........",
Field.Store.YES, Field.Index.TOKENIZED);
doc2.add(field2);

writer2.addDocument(doc2);

// 在這裏可以添加其他文件

/* 關閉 */
writer2.close();
}

public void search(String serchString) throws Exception {
/* 創建一個搜索,搜索剛纔創建的d:\\save\\目錄下的索引 */
IndexSearcher indexSearcher = new IndexSearcher("d:\\save\\");
/* 在這裏我們只需要搜索一個目錄 */
IndexSearcher indexSearchers[] = { indexSearcher };
/* 我們需要搜索兩個域"ArticleTitle", "ArticleText"裏面的內容 */
String[] fields = { "ArticleTitle", "ArticleText" };
/* 下面這個表示要同時搜索這兩個域,而且只要一個域裏面有滿足我們搜索的內容就行
BooleanClause.Occur.MUST表示and,
BooleanClause.Occur.MUST_NOT表示not,
BooleanClause.Occur.SHOULD表示or.
*/
BooleanClause.Occur[] clauses = { BooleanClause.Occur.SHOULD,
BooleanClause.Occur.SHOULD };
/*
* MultiFieldQueryParser表示多個域解析,
* 同時可以解析含空格的字符串,如果我們搜索"中國 金牌",根據前面的索引,顯然搜到的是第二份文件
*/
Query query = MultiFieldQueryParser.parse(serchString, fields, clauses,
new StandardAnalyzer());
/* Multisearcher表示多目錄搜索,在這裏我們只有一個目錄 */
MultiSearcher searcher = new MultiSearcher(indexSearchers);
/* 開始搜索 */
Hits h = searcher.search(query);
/* 把搜索出來的所有文件打印出來 */
for (int i = 0; i < h.length(); i++) {
/* 打印出文件裏面ArticleTitle域裏面的內容 */
System.out.println(h.doc(i).get("ArticleTitle"));
/* 打印出文件裏面ArticleText域裏面的內容 */
System.out.println(h.doc(i).get("ArticleText"));
}
/* 關閉 */
searcher.close();
}
}

補充內容:
term
term是搜索的最小單位,它表示文檔的一個詞語,term由兩部分組成:它表示的詞語和這個詞語所出現
的field。
如:
Term term1=new Term("date","20090611");
date即是對應的field:即對應document中的field==> new Field("field名".....)
20090611即是對應的搜索值.
(3)
將索引直接寫在內存
你需要首先創建一個RAMDirectory,並將其傳給writer,代碼如下:
Directory dir = new RAMDirectory();
IndexWriter writer = new IndexWriter(dir, new StandardAnalyzer(), true);
Document doc = new Document();
doc.add(new Field("title", "lucene introduction", Field.Store.YES, Field.Index.TOKENIZED));
doc.add(new Field("content", "lucene works well", Field.Store.YES, Field.Index.TOKENIZED));
writer.addDocument(doc);
writer.optimize();
writer.close();
(4)
如何刪除索引
lucene本身支持兩種刪除模式
1,DeleteDocument(int docNum)
2,DeleteDocuments(Term term)
一般使用的是第二種.這種方法實際上是首先根據參數term執行一個搜索操作,然後把搜索到的結果批量
刪除了。我們可以通過這個方法提供一個嚴格的查詢條件,達到刪除指定document的目的。
下面給出一個例子:
Directory dir = FSDirectory.getDirectory(PATH, false);
IndexReader reader = IndexReader.open(dir);
Term term = new Term(field, key);
reader.deleteDocuments(term);
reader.close();
(5)
如何更新索引
lucene並沒有提供專門的索引更新方法,我們需要先將相應的document刪除,然後再將新的document加
入索引。例如:
Directory dir = FSDirectory.getDirectory(PATH, false);
IndexReader reader = IndexReader.open(dir);
Term term = new Term(“title”, “lucene introduction”);
reader.deleteDocuments(term);
reader.close();
IndexWriter writer = new IndexWriter(dir, new StandardAnalyzer(), true);
Document doc = new Document();
doc.add(new Field("title", "lucene introduction", Field.Store.YES, Field.Index.TOKENIZED));
doc.add(new Field("content", "lucene is funny", Field.Store.YES, Field.Index.TOKENIZED));
writer.addDocument(doc);
writer.optimize();
writer.close();

[2]
各種各樣的Query
下面我們看看lucene到底允許我們進行哪些查詢操作:
(1)
TermQuery
首先介紹最基本的查詢,如果你想執行一個這樣的查詢:“在content域中包含‘lucene’的document”
,那麼你可以用TermQuery:
Term t = new Term("content", " lucene";
Query query = new TermQuery(t);
(2)
PhraseQuery
你可能對中日關係比較感興趣,想查找‘中’和‘日’捱得比較近(5個字的距離內)的文章,超過這個
距離的不予考慮,你可以:
PhraseQuery query = new PhraseQuery();
query.setSlop(5);
query.add(new Term("content ", “中”));
query.add(new Term(“content”, “日”));
PrefixQuery
如果你想搜以‘中’開頭的詞語,你可以用PrefixQuery:
PrefixQuery query = new PrefixQuery(new Term("content ", "中");
(3)
還有:
BooleanQuery
WildcardQuery
FuzzyQuery
RangeQuery
(4)
QueryParser,可以對上面各種query進行整合實現.
下面我們對應每種Query在QueryParser中演示一下:
TermQuery可以用“field:key”方式,例如“content:lucene”。
BooleanQuery中‘與’用‘+’,‘或’用‘ ’,例如“content:java contenterl”。
WildcardQuery仍然用‘?’和‘*’,例如“content:use*”。
PhraseQuery用‘~’,例如“content:"中日"~5”。
PrefixQuery用‘*’,例如“中*”。
FuzzyQuery用‘~’,例如“content: wuzza ~”。
RangeQuery用‘[]’或‘{}’,前者表示閉區間,後者表示開區間,例如“time:[20060101 TO
20060130]”,注意TO區分大小寫。
你可以任意組合query string,完成複雜操作.
如:
Directory dir = FSDirectory.getDirectory(PATH, false);
IndexSearcher is = new IndexSearcher(dir);
QueryParser parser = new QueryParser("content", new StandardAnalyzer());
Query query = parser.parse("+(title:lucene content:lucene) +time:[20060101 TO 20060130]";
Hits hits = is.search(query);
即表示:
標題或正文包括lucene,並且時間在20060101到20060130之間的文章.

(5)
Filter
filter的作用就是限制只查詢索引的某個子集,它的作用有點像SQL語句裏的 where,但又有區別,它不是正規查詢的一部分,只是對數據源進行預處理,然後交給查詢語句。注意它執行的是預處理,而不是對查詢結果進行過濾,所以使用filter的代價是很大的,它可能會使一次查詢耗時提高一百倍。
最常用的filter是RangeFilter和QueryFilter。RangeFilter是設定只搜索指定範圍內的索引;QueryFilter是在上次查詢的結果中搜索。
Filter的使用非常簡單,你只需創建一個filter實例,然後把它傳給searcher。繼續上面的例子,查詢“時間在20060101到20060130之間的文章”除了將限制寫在query string中,你還可以寫在RangeFilter
中:
Directory dir = FSDirectory.getDirectory(PATH, false);
IndexSearcher is = new IndexSearcher(dir);
QueryParser parser = new QueryParser("content", new StandardAnalyzer());
Query query = parser.parse("title:lucene content:lucene";
RangeFilter filter = new RangeFilter("time", "20060101", "20060230", true, true);
Hits hits = is.search(query, filter);
(6)
Sort
有時你想要一個排好序的結果集,就像SQL語句的“order by”,lucene能做到:通過Sort。
Sort sort Sort(“time”); //相當於SQL的“order by time”
Sort sort = new Sort(“time”, true); // 相當於SQL的“order by time desc”
下面是一個完整的例子:
Directory dir = FSDirectory.getDirectory(PATH, false);
IndexSearcher is = new IndexSearcher(dir);
QueryParser parser = new QueryParser("content", new StandardAnalyzer());
Query query = parser.parse("title:lucene content:lucene";
RangeFilter filter = new RangeFilter("time", "20060101", "20060230", true, true);
Sort sort = new Sort(“time”);
Hits hits = is.search(query, filter, sort);

(7)
優化搜索性能
由以上數據可以得出結論:
1、 儘量降低時間精度,將精度由秒換成天帶來的性能提高甚至比使用cache還好,最好不使用filter。
2、 在不能降低時間精度的情況下,使用cache能帶了10倍左右的性能提高。

Lucene的中文分詞器(PaodingAnalyzer)+高亮顯示
[1]
分詞功能介紹
分詞模塊對於搜索是很重要的。搜索其實就是對分詞的每一個片段進行查詢,檢查是否匹配.
(1)
Lucene版本是lucene-2.4.0,它(如standardAnalyzer)已經能夠支持中文分詞,但它是採用一元分詞(逐字拆分)的方法,即把每一個漢字當作是一個詞,這樣會:
使建立的索引非常龐大,
檢索緩慢,
同時查詢的準確性不高,會影響查詢效率.
如:
字符串"中華爲何怎麼做",用standardAnalyzer一元切詞,結果如下:
中 華 爲 何 怎 麼 做
當我們想查詢"華爲"時,"華爲"分爲"華 爲",分別與上面的"華 爲"相匹配.但這不是我們想查詢的結果.
又如:搜索“和服”會出現“產品和服務”,搜索“海爾”會出現“海爾德”.
所以有必要給文本增加詞的邊界信息以提高檢索精確度。這裏我就介紹最爲常用的"庖丁解牛" 分詞包.
(2)
對於英語分詞
以單詞之間的間隔來劃分.
無論是standardAnalyzer,PaodingAnalyzer,英文的切詞,一個連接的單詞,不會被切成兩分.如:
AttachFileSize,只能切爲AttachFileSize;
Attach File Size,會被切成Attach File Size
若要用Attach去查找AttachFileSize,則須用通配符.
而中文切詞上:如:"中華爲何怎麼做"
standardAnalyzer:一元切詞(如:中 華 爲 何 怎 麼 做)
PaodingAnalyzer:二元切詞(如:中華 華爲 爲何 怎麼 做 )

(3)
安裝"庖丁解牛"分詞包:
庖丁中文分詞需要一套詞典,這些詞典需要統一存儲在某個目錄下,這個目錄稱爲詞典安裝目錄。詞典安裝目錄可以是文件系統的任何目錄,它不依賴於應用程序的運行目錄。將詞典拷貝到詞典安裝目錄的過程稱爲安裝詞典。增加、刪除、修改詞典目錄下的詞典的過程稱爲自定製詞典。
其安裝的方法:
1.
"庖丁解牛"的下載地址是http://code.google.com/p/paoding/downloads/list,下載好後解壓,我解壓在E:\paoding2_0_4;
2.
進入該目錄,首先將paoding-analysis.jar拷貝到項目的WEB-INF/lib目錄;
3.
接着需要設置環境變量PAODING_DIC_HOME,變量名:PAODING_DIC_HOME 變量值:E:\paoding2_0_4\dic
4.
第三步將E:\paoding2_0_4\src目錄下的paoding-dic-home.properties屬性文件拷貝到項目的src目錄下(注意是src的根目錄下),添加一行aoding.dic.home=E:/paoding2_0_4/dic.
好了,到這裏,已經完成了Lucene和"庖丁解牛"的整合.
(4)
查看各種分詞器的分詞情況:
public static void main(String[] args) throws Exception {
//StandardAnalyzer: 一元分詞
//Analyzer analyzer = new StandardAnalyzer();
//PaodingAnalyzer: 二元分詞
Analyzer analyzer = new PaodingAnalyzer();
String indexStr = "我的QQ號碼是58472399";
StringReader reader = new StringReader(indexStr);
TokenStream ts = analyzer.tokenStream(indexStr, reader);
Token t = ts.next();
while (t != null) {
System.out.print(t.termText()+" ");
t = ts.next();
}
}
StandardAnalyzer分詞結果:我 的 qq 號 碼 是 58472399
PaodingAnalyzer分詞結果:我的 qq 號碼 58472399
如果把indexStr換成是"中華人民共和國萬歲" ,那麼分詞結果爲:
中華 華人 人民 共和 共和國 萬歲
很明顯,它的分詞效果要比Lucene自帶的中文分詞器的效果好的多.Lucene自帶分詞器是將中文逐字拆分的,這是最爲原始的分詞方法,現在大都不採用.

[2]
關鍵字高亮顯示
關鍵字高亮顯示也就是在頁面顯示時,事先對要顯示的內容處理,抽取出關鍵字並加亮,這裏抽取關鍵字也是用lucene,lucene自帶有heightlight包就可以實現此功能。
Highlighter包括了三個主要部分:段劃分器(Fragmenter)、計分器(Scorer)和格式化器(Formatter)。
通常要用到的幾個重要類有:
import org.apache.lucene.search.highlight.Highlighter;
import org.apache.lucene.search.highlight.QueryScorer;
import org.apache.lucene.search.highlight.SimpleFragmenter;
import org.apache.lucene.search.highlight.SimpleHTMLFormatter;
1)SimpleFragmenter
Highlighter利用Fragmenter將原始文本分割成多個片段。內置的SimpleFragmenter將原始文本分割成相同大小的片段,片段默認的大小爲100個字符。這個大小是可控制的。
2)SimpleHTMLFormatter:用來控制你要加亮的關鍵字的高亮方式
此類有2個構造方法
1:SimpleHTMLFormatter()默認的構造方法.加亮方式:關鍵字
相當於如下代碼:
Formatter formatter = new Formatter() {
public String highlightTerm(String srcText, TokenGroup g) {
if (g.getTotalScore() <= 0) {
return srcText;
}
return "" + srcText + "";
}
};
2:SimpleHTMLFormatter(String preTag, String postTag).加亮方式:preTag關鍵字postTag
3)QueryScorer
QueryScorer 是內置的計分器。計分器的工作首先是將片段排序。QueryScorer使用的項是從用戶輸入的查詢中得到的;它會從原始輸入的單詞、詞組和布爾查詢中提取項,並且基於相應的加權因子(boost factor)給它們加權。
爲了便於QueryScoere使用,還必須對查詢的原始形式進行重寫。比如,帶通配符查詢、模糊查詢、前綴查詢以及範圍查詢 等,都被重寫爲BoolenaQuery中所使用的項。在將Query實例傳遞到QueryScorer之前,可以調用Query.rewrite (IndexReader)方法來重寫Query對象.
[3]
實例之
使用Lucene+Paoding構建SSH2系統的站內搜索
說明:
(1)
用paodingAnalyzer進行分詞的操作,與用standardAnalyzer幾乎一樣,只是:
在創建索引時:

IndexWriter writer = new IndexWriter(indexPath,new PaodingAnalyzer(), true);
代替:
IndexWriter writer = new IndexWriter(indexPath,new StandardAnalyzer(), true);
用:
doc.add(new Field
("id",id,Field.Store.YES,Field.Index.TOKENIZED,Field.TermVector.WITH_POSITIONS_OFFSETS));
代替:
doc.add(new Field("id",id,Field.Store.YES,Field.Index.TOKENIZED));
在搜索時,
用:
QueryParser parser = new QueryParser("id",new PaodingAnalyzer());
代替:
QueryParser parser = new QueryParser("id",new StandardAnalyzer());
如下:
public static Document createConfigDoc(String id,String confContext)
{
Document doc=new Document();
//採用默認的standardAnalyzer
// doc.add(new Field("id",id,Field.Store.YES,Field.Index.TOKENIZED));
// doc.add(new Field
("confContext",confContext,Field.Store.YES,Field.Index.TOKENIZED));
//採用paodingAnalyzer
doc.add(new Field
("id",id,Field.Store.YES,Field.Index.TOKENIZED,Field.TermVector.WITH_POSITIONS_OFFSETS));
doc.add(new Field
("confContext",confContext,Field.Store.YES,Field.Index.TOKENIZED,Field.TermVector.WITH_POSI
TIONS_OFFSETS));
return doc;
}
(2)
對於高亮顯示:請見代碼;
如下:
try {
IndexReader reader = IndexReader.open(indexPath);
IndexSearcher searcher = new IndexSearcher(indexPath);
QueryParser parser = new QueryParser("id",analyzer);
Query query=parser.parse(searchMess);
query = query.rewrite(reader);
System.out.println("Searching for: " + query.toString());
hits=searcher.search(query);

String SearchMess="";
String SearchValue="";
for (int i = 0; i < hits.length(); i++)
{
String highLightText="";
Document doc = hits.doc(i);
SearchMess=doc.get("id");
SearchValue=doc.get("confContext");
System.out.println("-----高亮前------SearchMess:\n\t" +
SearchMess);
// 設置高亮
SimpleHTMLFormatter formatter = new SimpleHTMLFormatter
("", "");
Highlighter highlighter = new Highlighter(formatter, new
QueryScorer(query));
//Highlighter利用Fragmenter將原始文本分割成多個片段。片段默
認的大小爲100個字符。
//設置每一片段的大小
highlighter.setTextFragmenter(new SimpleFragmenter(200));
if (SearchMess != null) {
//id:爲上面查詢分析器中設置查詢的項:QueryParser
parser = new QueryParser("id",analyzer);
//SearchMess:爲上面查詢結果中的內容:Document doc =
hits.doc(i);SearchMess=doc.get("id");
//若設置出錯,則高亮顯示爲null
TokenStream tokenStream = analyzer.tokenStream("id",new
StringReader(SearchMess));
//highLightText,則是高亮顯示的結果.下面的highLightText,輸出
格式如:
//中華爲何
highLightText = highlighter.getBestFragment
(tokenStream,SearchMess);
v.add(highLightText);
v.add(SearchValue);
System.out.println("-----高亮後------highLightText:\n\t" +
highLightText);
}
}
searcher.close();
} catch (Exception e) {
e.printStackTrace();
}
(3)
對於創建索引,與搜索過程:
創建索引,
(若搜索文件比較小,也可是:創建索引-->進行搜索.但一般情況下,採用創建索引,與搜索過程分離.如下)
我們可以利用Quartz在夜間自動重新創建lucene最新的index,從而保證索引內容與數據庫中的內容相對
比較一致(相當於舊索引的更新.因爲用戶可能會修改原記錄的內容,所以索引對應的內容也應該被相應的更新);
當用戶進行搜索時,我們進行:
當用戶進行搜索時,可以設置"增量索引"的方式,把新增的DB記錄追加到索引文件中,從而保證用戶最新的消息可以被搜索到.
然後再進行搜索操作.
如下:
增量索引方式,可以如上面的實現那樣設置;這裏只列出定時創建索引代碼:
創建索引代碼:
public void createLuceneIndex()
{
String idPath="D:/tomcat_bbs/webapps/BBSCS_8_0_3/storeId.txt";
String indexPath="D:/tomcat_bbs/webapps/BBSCS_8_0_3/index";
try {
//for iBatis
List bbsConfigs = (List) sqlMapClientTemplate.queryForList
("getAllBBSConfig");
//IndexWriter writer = new IndexWriter(indexPath,new
StandardAnalyzer(), true);
IndexWriter writer = new IndexWriter(indexPath,new PaodingAnalyzer
(), true);
for (int a = 0; a < bbsConfigs.size(); a++) {
BBSConfig bbsConfig = (BBSConfig) bbsConfigs.get(a);
Document doc = JLuceneUtils.createConfigDoc
(bbsConfig.getId(), bbsConfig.getConfContext());
writer.addDocument(doc);
}
writer.optimize(); //優化索引文件
writer.close();
System.out.println("-------------定時創建lucene的Indexe success!");
} catch (Exception e) {
e.printStackTrace();
}
}
定時代碼:


class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">




createLuceneIndex



class="org.springframework.scheduling.quartz.CronTriggerBean">




0 0 4 * * ?




class="org.springframework.scheduling.quartz.SchedulerFactoryBean">





-->此處



(4)
最後調用後,將highLightText放於session中,通過return 'search';返回到前臺,再獲取顯示.因爲是前臺是HTML/JSP頁面,所以,如:highLightText="中華爲何",就會顯示出高亮效果.若用system.out打印,則只能看到:
中華爲何



原文地址:http://www.lingcn.com/Blogs/bb6484e9-0611-47ac-8484-ab20046d5389.aspx
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章