周 登朋
(zhoudengpeng@yahoo.com.cn), 軟件工程師, 上海交通大學(xué)
2006 年 9 月 06 日?
在本篇文章中,你會(huì)學(xué)習(xí)到如何利用 Lucene 實(shí)現(xiàn)高級(jí)搜索功能以及如何利用 Lucene 來創(chuàng)建 Web 搜索應(yīng)用程序。通過這些學(xué)習(xí),你就可以利用 Lucene 來創(chuàng)建自己的搜索應(yīng)用程序。
架構(gòu)概覽
通常一個(gè) Web 搜索引擎的架構(gòu)分為前端和后端兩部分,就像圖一中
所示。在前端流程中,用戶在搜索引擎提供的界面中輸入要搜索的關(guān)鍵詞,這里提到的用戶界面一般是一個(gè)帶有輸入框的 Web
頁面,然后應(yīng)用程序?qū)⑺阉鞯年P(guān)鍵詞解析成搜索引擎可以理解的形式,并在索引文件上進(jìn)行搜索操作。在排序后,搜索引擎返回搜索結(jié)果給用戶。在后端流程中,網(wǎng)
絡(luò)爬蟲或者機(jī)器人從因特網(wǎng)上獲取 Web 頁面,然后索引子系統(tǒng)解析這些 Web 頁面并存入索引文件中。如果你想利用 Lucene 來創(chuàng)建一個(gè)
Web 搜索應(yīng)用程序,那么它的架構(gòu)也和上面所描述的類似,就如圖一中所示。
Figure 1. Web 搜索引擎架構(gòu)
利用 Lucene 實(shí)現(xiàn)高級(jí)搜索
Lucene 支持多種形式的高級(jí)搜索,我們?cè)谶@一部分中會(huì)進(jìn)行探討,然后我會(huì)使用 Lucene 的 API 來演示如何實(shí)現(xiàn)這些高級(jí)搜索功能。
布爾操作符
大多數(shù)的搜索引擎都會(huì)提供布爾操作符讓用戶可以組合查詢,典型的布爾操作符有 AND, OR, NOT。Lucene 支持 5 種布爾操作符,分別是 AND, OR, NOT, 加(+), 減(-)。接下來我會(huì)講述每個(gè)操作符的用法。
-
OR:
如果你要搜索含有字符 A 或者 B 的文檔,那么就需要使用 OR
操作符。需要記住的是,如果你只是簡(jiǎn)單的用空格將兩個(gè)關(guān)鍵詞分割開,其實(shí)在搜索的時(shí)候搜索引擎會(huì)自動(dòng)在兩個(gè)關(guān)鍵詞之間加上 OR
操作符。例如,“Java OR Lucene” 和 “Java Lucene” 都是搜索含有 Java 或者含有 Lucene 的文檔。
-
AND: 如果你需要搜索包含一個(gè)以上關(guān)鍵詞的文檔,那么就需要使用 AND 操作符。例如,“Java AND Lucene” 返回所有既包含 Java 又包含 Lucene 的文檔。
-
NOT:
Not 操作符使得包含緊跟在 NOT 后面的關(guān)鍵詞的文檔不會(huì)被返回。例如,如果你想搜索所有含有 Java 但不含有 Lucene
的文檔,你可以使用查詢語句 “Java NOT Lucene”。但是你不能只對(duì)一個(gè)搜索詞使用這個(gè)操作符,比如,查詢語句 “NOT Java”
不會(huì)返回任何結(jié)果。
-
加號(hào)(+): 這個(gè)操作符的作用和 AND 差不多,但它只對(duì)緊跟著它的一個(gè)搜索詞起作用。例如,如果你想搜索一定包含 Java,但不一定包含 Lucene 的文檔,就可以使用查詢語句“+Java Lucene”。
-
減號(hào)(-): 這個(gè)操作符的功能和 NOT 一樣,查詢語句 “Java -Lucene” 返回所有包含 Java 但不包含 Lucene 的文檔。
接下來我們看一下如何利用 Lucene 提供的 API 來實(shí)現(xiàn)布爾查詢。清單1 顯示了如果利用布爾操作符進(jìn)行查詢的過程。
清單1:使用布爾操作符
??
//
Test?boolean?operator
public
?
void
?testOperator(String?indexDirectory)?
throws
?Exception
...
{
???Directory?dir?
=
?FSDirectory.getDirectory(indexDirectory,
false
);
???IndexSearcher?indexSearcher?
=
?
new
?IndexSearcher(dir);

???String[]?searchWords?
=
?
...
{
"
Java?AND?Lucene
"
,?
"
Java?NOT?Lucene
"
,?
"
Java?OR?Lucene
"
,?
????????????????????
"
+Java?+Lucene
"
,?
"
+Java?-Lucene
"
}
;
???Analyzer?language?
=
?
new
?StandardAnalyzer();
???Query?query;

???
for
(
int
?i?
=
?
0
;?i?
<
?searchWords.length;?i
++
)
...
{
??????query?
=
?QueryParser.parse(searchWords[i],?
"
title
"
,?language);
??????Hits?results?
=
?indexSearcher.search(query);
??????System.out.println(results.length()?
+
?
"
search?results?for?query?
"
?
+
?searchWords[i]);
???}
}
域搜索(Field Search)
Lucene 支持域搜索,你可以指定一次查詢是在哪些域(Field)上進(jìn)行。例如,如果索引的文檔包含兩個(gè)域,Title
和 Content
,你就可以使用查詢 “Title: Lucene AND Content: Java” 來返回所有在 Title 域上包含 Lucene 并且在 Content 域上包含 Java 的文檔。清單 2 顯示了如何利用 Lucene 的 API 來實(shí)現(xiàn)域搜索。
清單2:實(shí)現(xiàn)域搜索
//
Test?field?search
public
?
void
?testFieldSearch(String?indexDirectory)?
throws
?Exception
...
{
????Directory?dir?
=
?FSDirectory.getDirectory(indexDirectory,
false
);
????IndexSearcher?indexSearcher?
=
?
new
?IndexSearcher(dir);
????String?searchWords?
=
?
"
title:Lucene?AND?content:Java
"
;
????Analyzer?language?
=
?
new
?StandardAnalyzer();
????Query?query?
=
?QueryParser.parse(searchWords,?
"
title
"
,?language);
????Hits?results?
=
?indexSearcher.search(query);
????System.out.println(results.length()?
+
?
"
search?results?for?query?
"
?
+
?searchWords);
}
通配符搜索(Wildcard Search)
Lucene
支持兩種通配符:?jiǎn)柼?hào)(?)和星號(hào)(*)。你可以使用問號(hào)(?)來進(jìn)行單字符的通配符查詢,或者利用星號(hào)(*)進(jìn)行多字符的通配符查詢。例如,如果你想搜
索 tiny 或者 tony,你就可以使用查詢語句 “t?ny”;如果你想查詢 Teach, Teacher 和
Teaching,你就可以使用查詢語句 “Teach*”。清單3 顯示了通配符查詢的過程。
清單3:進(jìn)行通配符查詢
//
Test?wildcard?search
public
?
void
?testWildcardSearch(String?indexDirectory)
throws
?Exception
...
{
???Directory?dir?
=
?FSDirectory.getDirectory(indexDirectory,
false
);
???IndexSearcher?indexSearcher?
=
?
new
?IndexSearcher(dir);

???String[]?searchWords?
=
?
...
{
"
tex*
"
,?
"
tex?
"
,?
"
?ex*
"
}
;
???Query?query;

???
for
(
int
?i?
=
?
0
;?i?
<
?searchWords.length;?i
++
)
...
{
??????query?
=
?
new
?WildcardQuery(
new
?Term(
"
title
"
,searchWords[i]));
??????Hits?results?
=
?indexSearcher.search(query);
??????System.out.println(results.length()?
+
?
"
search?results?for?query?
"
?
+
?searchWords[i]);
???}
}
模糊查詢
Lucene 提供的模糊查詢基于編輯距離算法(Edit distance algorithm)。你可以在搜索詞的尾部加上字符 ~ 來進(jìn)行模糊查詢。例如,查詢語句 “think~” 返回所有包含和 think 類似的關(guān)鍵詞的文檔。清單 4 顯示了如果利用 Lucene 的 API 進(jìn)行模糊查詢的代碼。
清單4:實(shí)現(xiàn)模糊查詢
//
Test?fuzzy?search
public
?
void
?testFuzzySearch(String?indexDirectory)
throws
?Exception
...
{
???Directory?dir?
=
?FSDirectory.getDirectory(indexDirectory,
false
);
???IndexSearcher?indexSearcher?
=
?
new
?IndexSearcher(dir);

???String[]?searchWords?
=
?
...
{
"
text
"
,?
"
funny
"
}
;
???Query?query;

???
for
(
int
?i?
=
?
0
;?i?
<
?searchWords.length;?i
++
)
...
{
??????query?
=
?
new
?FuzzyQuery(
new
?Term(
"
title
"
,searchWords[i]));
??????Hits?results?
=
?indexSearcher.search(query);
??????System.out.println(results.length()?
+
?
"
search?results?for?query?
"
?
+
?searchWords[i]);
???}
}
范圍搜索(Range Search)
范圍搜索匹配某個(gè)域上的值在一定范圍的文檔。例如,查詢 “age:[18 TO 35]” 返回所有 age 域上的值在 18 到 35 之間的文檔。清單5顯示了利用 Lucene 的 API 進(jìn)行返回搜索的過程。
清單5:測(cè)試范圍搜索
//
Test?range?search
public
?
void
?testRangeSearch(String?indexDirectory)
throws
?Exception
...
{
????Directory?dir?
=
?FSDirectory.getDirectory(indexDirectory,
false
);
????IndexSearcher?indexSearcher?
=
?
new
?IndexSearcher(dir);
????Term?begin?
=
?
new
?Term(
"
birthDay
"
,
"
20000101
"
);
????Term?end???
=
?
new
?Term(
"
birthDay
"
,
"
20060606
"
);
????Query?query?
=
?
new
?RangeQuery(begin,end,
true
);
????Hits?results?
=
?indexSearcher.search(query);
????System.out.println(results.length()?
+
?
"
search?results?is?returned
"
);
}
在 Web 應(yīng)用程序中集成 Lucene
接下來我們開發(fā)一個(gè) Web 應(yīng)用程序利用 Lucene 來檢索存放在文件服務(wù)器上的 HTML 文檔。在開始之前,需要準(zhǔn)備如下環(huán)境:
- Eclipse 集成開發(fā)環(huán)境
- Tomcat 5.0
- Lucene Library
- JDK 1.5
這個(gè)例子使用 Eclipse 進(jìn)行 Web 應(yīng)用程序的開發(fā),最終這個(gè) Web 應(yīng)用程序跑在 Tomcat 5.0 上面。在準(zhǔn)備好開發(fā)所必需的環(huán)境之后,我們接下來進(jìn)行 Web 應(yīng)用程序的開發(fā)。
1、創(chuàng)建一個(gè)動(dòng)態(tài) Web 項(xiàng)目
- 在 Eclipse 里面,選擇 File > New > Project,然后再彈出的窗口中選擇動(dòng)態(tài) Web 項(xiàng)目,如圖二所示。
圖二:創(chuàng)建動(dòng)態(tài)Web項(xiàng)目
- 在創(chuàng)建好動(dòng)態(tài) Web 項(xiàng)目之后,你會(huì)看到創(chuàng)建好的項(xiàng)目的結(jié)構(gòu),如圖三所示,項(xiàng)目的名稱為 sample.dw.paper.lucene。
圖三:動(dòng)態(tài) Web 項(xiàng)目的結(jié)構(gòu)
2. 設(shè)計(jì) Web 項(xiàng)目的架構(gòu)
在我們的設(shè)計(jì)中,把該系統(tǒng)分成如下四個(gè)子系統(tǒng):
-
用戶接口: 這個(gè)子系統(tǒng)提供用戶界面使用戶可以向 Web 應(yīng)用程序服務(wù)器提交搜索請(qǐng)求,然后搜索結(jié)果通過用戶接口來顯示出來。我們用一個(gè)名為 search.jsp 的頁面來實(shí)現(xiàn)該子系統(tǒng)。
-
請(qǐng)求管理器: 這個(gè)子系統(tǒng)管理從客戶端發(fā)送過來的搜索請(qǐng)求并把搜索請(qǐng)求分發(fā)到搜索子系統(tǒng)中。最后搜索結(jié)果從搜索子系統(tǒng)返回并最終發(fā)送到用戶接口子系統(tǒng)。我們使用一個(gè) Servlet 來實(shí)現(xiàn)這個(gè)子系統(tǒng)。
-
搜索子系統(tǒng): 這個(gè)子系統(tǒng)負(fù)責(zé)在索引文件上進(jìn)行搜索并把搜索結(jié)構(gòu)傳遞給請(qǐng)求管理器。我們使用 Lucene 提供的 API 來實(shí)現(xiàn)該子系統(tǒng)。
-
索引子系統(tǒng): 這個(gè)子系統(tǒng)用來為 HTML 頁面來創(chuàng)建索引。我們使用 Lucene 的 API 以及 Lucene 提供的一個(gè) HTML 解析器來創(chuàng)建該子系統(tǒng)。
圖4
顯示了我們?cè)O(shè)計(jì)的詳細(xì)信息,我們將用戶接口子系統(tǒng)放到 webContent 目錄下面。你會(huì)看到一個(gè)名為 search.jsp 的頁面在這個(gè)文件夾里面。請(qǐng)求管理子系統(tǒng)在包 sample.dw.paper.lucene.servlet
下面,類 SearchController
負(fù)責(zé)功能的實(shí)現(xiàn)。搜索子系統(tǒng)放在包 sample.dw.paper.lucene.search
當(dāng)中,它包含了兩個(gè)類,SearchManager
和 SearchResultBean
,第一個(gè)類用來實(shí)現(xiàn)搜索功能,第二個(gè)類用來描述搜索結(jié)果的結(jié)構(gòu)。索引子系統(tǒng)放在包 sample.dw.paper.lucene.index
當(dāng)中。類 IndexManager
負(fù)責(zé)為 HTML 文件創(chuàng)建索引。該子系統(tǒng)利用包 sample.dw.paper.lucene.util
里面的類 HTMLDocParser
提供的方法 getTitle
和 getContent
來對(duì) HTML 頁面進(jìn)行解析。
圖四:項(xiàng)目的架構(gòu)設(shè)計(jì)
3. 子系統(tǒng)的實(shí)現(xiàn)
在分析了系統(tǒng)的架構(gòu)設(shè)計(jì)之后,我們接下來看系統(tǒng)實(shí)現(xiàn)的詳細(xì)信息。
-
用戶接口: 這個(gè)子系統(tǒng)有一個(gè)名為 search.jsp 的 JSP 文件來實(shí)現(xiàn),這個(gè) JSP 頁面包含兩個(gè)部分。第一部分提供了一個(gè)用戶接口去向 Web 應(yīng)用程序服務(wù)器提交搜索請(qǐng)求,如圖5所示。注意到這里的搜索請(qǐng)求發(fā)送到了一個(gè)名為 SearchController 的 Servlet 上面。Servlet 的名字和具體實(shí)現(xiàn)的類的對(duì)應(yīng)關(guān)系在 web.xml 里面指定。
圖5:向Web服務(wù)器提交搜索請(qǐng)求
這個(gè)JSP的第二部分負(fù)責(zé)顯示搜索結(jié)果給用戶,如圖6所示:
圖6:顯示搜索結(jié)果
-
請(qǐng)求管理器: 一個(gè)名為
SearchController
的 servlet 用來實(shí)現(xiàn)該子系統(tǒng)。清單6給出了這個(gè)類的源代碼。
清單6:請(qǐng)求管理器的實(shí)現(xiàn)
package
?sample.dw.paper.lucene.servlet;

import
?java.io.IOException;
import
?java.util.List;

import
?javax.servlet.RequestDispatcher;
import
?javax.servlet.ServletException;
import
?javax.servlet.http.HttpServlet;
import
?javax.servlet.http.HttpServletRequest;
import
?javax.servlet.http.HttpServletResponse;

import
?sample.dw.paper.lucene.search.SearchManager;


/**?*/
/**
?*?This?servlet?is?used?to?deal?with?the?search?request
?*?and?return?the?search?results?to?the?client
?
*/
public
?
class
?SearchController?
extends
?HttpServlet
...
{

????
private
?
static
?
final
?
long
?serialVersionUID?
=
?
1L
;

????
public
?
void
?doPost(HttpServletRequest?request,?HttpServletResponse?response)

??????????????????????
throws
?IOException,?ServletException
...
{
????????String?searchWord?
=
?request.getParameter(
"
searchWord
"
);
????????SearchManager?searchManager?
=
?
new
?SearchManager(searchWord);
????????List?searchResult?
=
?
null
;
????????searchResult?
=
?searchManager.search();
????????RequestDispatcher?dispatcher?
=
?request.getRequestDispatcher(
"
search.jsp
"
);
????????request.setAttribute(
"
searchResult
"
,searchResult);
????????dispatcher.forward(request,?response);
????}
????
public
?
void
?doGet(HttpServletRequest?request,?HttpServletResponse?response)

?????????????????????
throws
?IOException,?ServletException
...
{
????????doPost(request,?response);
????}
}
在清單6中,doPost
方法從客戶端獲取搜索詞并創(chuàng)建類 SearchManager
的一個(gè)實(shí)例,其中類 SearchManager
在搜索子系統(tǒng)中進(jìn)行了定義。然后,SearchManager
的方法 search 會(huì)被調(diào)用。最后搜索結(jié)果被返回到客戶端。
-
搜索子系統(tǒng): 在這個(gè)子系統(tǒng)中,我們定義了兩個(gè)類:
SearchManager
和 SearchResultBean
。第一個(gè)類用來實(shí)現(xiàn)搜索功能,第二個(gè)類是個(gè)JavaBean,用來描述搜索結(jié)果的結(jié)構(gòu)。清單7給出了類 SearchManager
的源代碼。
清單7:搜索功能的實(shí)現(xiàn)
package
?sample.dw.paper.lucene.search;

import
?java.io.IOException;
import
?java.util.ArrayList;
import
?java.util.List;

import
?org.apache.lucene.analysis.Analyzer;
import
?org.apache.lucene.analysis.standard.StandardAnalyzer;
import
?org.apache.lucene.queryParser.ParseException;
import
?org.apache.lucene.queryParser.QueryParser;
import
?org.apache.lucene.search.Hits;
import
?org.apache.lucene.search.IndexSearcher;
import
?org.apache.lucene.search.Query;

import
?sample.dw.paper.lucene.index.IndexManager;


/**?*/
/**
?*?This?class?is?used?to?search?the?
?*?Lucene?index?and?return?search?results
?
*/
public
?
class
?SearchManager?
...
{
????
????
private
?String?searchWord;
????
????
private
?IndexManager?indexManager;
????
????
private
?Analyzer?analyzer;
????

????
public
?SearchManager(String?searchWord)
...
{
????????
this
.searchWord???
=
??searchWord;
????????
this
.indexManager?
=
??
new
?IndexManager();
????????
this
.analyzer?????
=
??
new
?StandardAnalyzer();
????}
????

????
/**?*/
/**
?????*?do?search
?????
*/
????
public
?List?search()
...
{
????????List?searchResult?
=
?
new
?ArrayList();

????????
if
(
false
?
==
?indexManager.ifIndexExist())
...
{

????????
try
?
...
{

????????????
if
(
false
?
==
?indexManager.createIndex())
...
{
????????????????
return
?searchResult;
????????????}
????????}
?
catch
?(IOException?e)?
...
{
??????????e.printStackTrace();
??????????
return
?searchResult;
????????}
????????}
????????
????????IndexSearcher?indexSearcher?
=
?
null
;


????????
try
...
{
????????????indexSearcher?
=
?
new
?IndexSearcher(indexManager.getIndexDir());

????????}
catch
(IOException?ioe)
...
{
????????????ioe.printStackTrace();
????????}
????????QueryParser?queryParser?
=
?
new
?QueryParser(
"
content
"
,analyzer);
????????Query?query?
=
?
null
;

????????
try
?
...
{
????????????query?
=
?queryParser.parse(searchWord);

????????}
?
catch
?(ParseException?e)?
...
{
??????????e.printStackTrace();
????????}
????????
if
(
null
?
!=
?query?
>>
?
null
?
!=
?indexSearcher)
...
{????????????

????????????
try
?
...
{
????????????????Hits?hits?
=
?indexSearcher.search(query);

????????????????
for
(
int
?i?
=
?
0
;?i?
<
?hits.length();?i?
++
)
...
{
????????????????????SearchResultBean?resultBean?
=
?
new
?SearchResultBean();
????????????????????resultBean.setHtmlPath(hits.doc(i).get(
"
path
"
));
????????????????????resultBean.setHtmlTitle(hits.doc(i).get(
"
title
"
));
????????????????????searchResult.add(resultBean);
????????????????}
????????????}
?
catch
?(IOException?e)?
...
{
????????????????e.printStackTrace();
????????????}
????????}
????????
return
?searchResult;
????}
}
在清單7中,注意到在這個(gè)類里面有三個(gè)私有屬性。第一個(gè)是 searchWord
,代表了來自客戶端的搜索詞。第二個(gè)是 indexManager
,代表了在索引子系統(tǒng)中定義的類 IndexManager
的一個(gè)實(shí)例。第三個(gè)是 analyzer
,代表了用來解析搜索詞的解析器。現(xiàn)在我們把注意力放在方法 search
上面。這個(gè)方法首先檢查索引文件是否已經(jīng)存在,如果已經(jīng)存在,那么就在已經(jīng)存在的索引上進(jìn)行檢索,如果不存在,那么首先調(diào)用類 IndexManager
提供的方法來創(chuàng)建索引,然后在新創(chuàng)建的索引上進(jìn)行檢索。搜索結(jié)果返回后,這個(gè)方法從搜索結(jié)果中提取出需要的屬性并為每個(gè)搜索結(jié)果生成類 SearchResultBean
的一個(gè)實(shí)例。最后這些 SearchResultBean
的實(shí)例被放到一個(gè)列表里面并返回給請(qǐng)求管理器。
在類 SearchResultBean
中,含有兩個(gè)屬性,分別是 htmlPath
和 htmlTitle
,以及這個(gè)兩個(gè)屬性的 get 和 set 方法。這也意味著我們的搜索結(jié)果包含兩個(gè)屬性:htmlPath
和 htmlTitle
,其中 htmlPath
代表了 HTML 文件的路徑,htmlTitle
代表了 HTML 文件的標(biāo)題。
-
索引子系統(tǒng): 類
IndexManager
用來實(shí)現(xiàn)這個(gè)子系統(tǒng)。清單8 給出了這個(gè)類的源代碼。
清單8:索引子系統(tǒng)的實(shí)現(xiàn)
package
?sample.dw.paper.lucene.index;

import
?java.io.File;
import
?java.io.IOException;
import
?java.io.Reader;

import
?org.apache.lucene.analysis.Analyzer;
import
?org.apache.lucene.analysis.standard.StandardAnalyzer;
import
?org.apache.lucene.document.Document;
import
?org.apache.lucene.document.Field;
import
?org.apache.lucene.index.IndexWriter;
import
?org.apache.lucene.store.Directory;
import
?org.apache.lucene.store.FSDirectory;

import
?sample.dw.paper.lucene.util.HTMLDocParser;


/**?*/
/**
?*?This?class?is?used?to?create?an?index?for?HTML?files
?*
?
*/
public
?
class
?IndexManager?
...
{

????
//
the?directory?that?stores?HTML?files?
????
private
?
final
?String?dataDir??
=
?
"
c:\dataDir
"
;

????
//
the?directory?that?is?used?to?store?a?Lucene?index
????
private
?
final
?String?indexDir?
=
?
"
c:\indexDir
"
;


????
/**?*/
/**
?????*?create?index
?????
*/
????
public
?
boolean
?createIndex()?
throws
?IOException
...
{

????????
if
(
true
?
==
?ifIndexExist())
...
{
????????????
return
?
true
;????
????????}
????????File?dir?
=
?
new
?File(dataDir);

????????
if
(
!
dir.exists())
...
{
????????????
return
?
false
;
????????}
????????File[]?htmls?
=
?dir.listFiles();
????????Directory?fsDirectory?
=
?FSDirectory.getDirectory(indexDir,?
true
);
????????Analyzer??analyzer????
=
?
new
?StandardAnalyzer();
????????IndexWriter?indexWriter?
=
?
new
?IndexWriter(fsDirectory,?analyzer,?
true
);

????????
for
(
int
?i?
=
?
0
;?i?
<
?htmls.length;?i
++
)
...
{
????????????String?htmlPath?
=
?htmls[i].getAbsolutePath();


????????????
if
(htmlPath.endsWith(
"
.html
"
)?
||
?htmlPath.endsWith(
"
.htm
"
))
...
{
????????????????addDocument(htmlPath,?indexWriter);
????????????}
????????}
????????indexWriter.optimize();
????????indexWriter.close();
????????
return
?
true
;

????}
????
/**?*/
/**
?????*?Add?one?document?to?the?Lucene?index
?????
*/
????
public
?
void
?addDocument(String?htmlPath,?IndexWriter?indexWriter)
...
{
????????HTMLDocParser?htmlParser?
=
?
new
?HTMLDocParser(htmlPath);
????????String?path????
=
?htmlParser.getPath();
????????String?title???
=
?htmlParser.getTitle();
????????Reader?content?
=
?htmlParser.getContent();

????????Document?document?
=
?
new
?Document();
????????document.add(
new
?Field(
"
path
"
,path,Field.Store.YES,Field.Index.NO));
????????document.add(
new
?Field(
"
title
"
,title,Field.Store.YES,Field.Index.TOKENIZED));
????????document.add(
new
?Field(
"
content
"
,content));

????????
try
?
...
{
??????????????indexWriter.addDocument(document);

????}
?
catch
?(IOException?e)?
...
{
??????????????e.printStackTrace();
??????????}
????}
????
/**?*/
/**
?????*?judge?if?the?index?exists?already
?????
*/
????
public
?
boolean
?ifIndexExist()
...
{
????????File?directory?
=
?
new
?File(indexDir);

????????
if
(
0
?
<
?directory.listFiles().length)
...
{
????????????
return
?
true
;

????????}
else
...
{
????????????
return
?
false
;
????????}
????}
????
public
?String?getDataDir()
...
{
????????
return
?
this
.dataDir;
????}
????
public
?String?getIndexDir()
...
{
????????
return
?
this
.indexDir;
????}
}
這個(gè)類包含兩個(gè)私有屬性,分別是 dataDir
和 indexDir
。dataDir
代表存放等待進(jìn)行索引的 HTML 頁面的路徑,indexDir
代表了存放 Lucene 索引文件的路徑。類 IndexManager
提供了三個(gè)方法,分別是 createIndex
, addDocument
和 ifIndexExist
。如果索引不存在的話,你可以使用方法 createIndex
去創(chuàng)建一個(gè)新的索引,用方法 addDocument
去向一個(gè)索引上添加文檔。在我們的場(chǎng)景中,一個(gè)文檔就是一個(gè) HTML 頁面。方法 addDocument
會(huì)調(diào)用由類 HTMLDocParser
提供的方法對(duì) HTML 文檔進(jìn)行解析。你可以使用最后一個(gè)方法 ifIndexExist
來判斷 Lucene 的索引是否已經(jīng)存在。
現(xiàn)在我們來看一下放在包 sample.dw.paper.lucene.util
里面的類 HTMLDocParser
。這個(gè)類用來從 HTML 文件中提取出文本信息。這個(gè)類包含三個(gè)方法,分別是 getContent
,getTitle
和 getPath
。第一個(gè)方法返回去除了 HTML 標(biāo)記的文本內(nèi)容,第二個(gè)方法返回 HTML 文件的標(biāo)題,最后一個(gè)方法返回 HTML 文件的路徑。清單9 給出了這個(gè)類的源代碼。
清單9:HTML 解析器
package
?sample.dw.paper.lucene.util;

import
?java.io.FileInputStream;
import
?java.io.FileNotFoundException;
import
?java.io.IOException;
import
?java.io.InputStream;
import
?java.io.InputStreamReader;
import
?java.io.Reader;
import
?java.io.UnsupportedEncodingException;

import
?org.apache.lucene.demo.html.HTMLParser;


public
?
class
?HTMLDocParser?
...
{
????
private
?String?htmlPath;

????
private
?HTMLParser?htmlParser;


????
public
?HTMLDocParser(String?htmlPath)
...
{
????????
this
.htmlPath?
=
?htmlPath;
????????initHtmlParser();
????}
????
private
?
void
?initHtmlParser()
...
{
????????InputStream?inputStream?
=
?
null
;

????????
try
?
...
{
????????????inputStream?
=
?
new
?FileInputStream(htmlPath);

????????}
?
catch
?(FileNotFoundException?e)?
...
{
????????????e.printStackTrace();
????????}
????????
if
(
null
?
!=
?inputStream)
...
{

????????????
try
?
...
{
????????????????htmlParser?
=
?
new
?HTMLParser(
new
?InputStreamReader(inputStream,?
"
utf-8
"
));

????????????}
?
catch
?(UnsupportedEncodingException?e)?
...
{
????????????????e.printStackTrace();
????????????}
????????}
????}
????
public
?String?getTitle()
...
{

????????
if
(
null
?
!=
?htmlParser)
...
{

????????????
try
?
...
{
????????????????
return
?htmlParser.getTitle();

????????????}
?
catch
?(IOException?e)?
...
{
????????????????e.printStackTrace();

????????????}
?
catch
?(InterruptedException?e)?
...
{
????????????????e.printStackTrace();
????????????}
????????}
????
return
?
""
;
????}
????
public
?Reader?getContent()
...
{

????
if
(
null
?
!=
?htmlParser)
...
{

????????????
try
?
...
{
??????????????????
return
?htmlParser.getReader();

??????????????}
?
catch
?(IOException?e)?
...
{
??????????????????e.printStackTrace();
??????????????}
????????}
????????
return
?
null
;
????}
????
public
?String?getPath()
...
{
????????
return
?
this
.htmlPath;????????
????}
}
5.在 Tomcat 5.0 上運(yùn)行應(yīng)用程序
現(xiàn)在我們可以在 Tomcat 5.0 上運(yùn)行開發(fā)好的應(yīng)用程序。
- 右鍵單擊 search.jsp,然后選擇 Run as > Run on Server,如圖7所示。
圖7:配置 Tomcat 5.0
- 在彈出的窗口中,選擇 Tomcat v5.0 Server 作為目標(biāo) Web 應(yīng)用程序服務(wù)器,然后點(diǎn)擊 Next,如圖8 所示:
圖8:選擇 Tomcat 5.0
- 現(xiàn)在需要指定用來運(yùn)行 Web 應(yīng)用程序的 Apache Tomcat 5.0 以及 JRE 的路徑。這里你所選擇的 JRE 的版本必須和你用來編譯 Java 文件的 JRE 的版本一致。配置好之后,點(diǎn)擊 Finish。如 圖9 所示。
圖9:完成Tomcat 5.0的配置
- 配置好之后,Tomcat 會(huì)自動(dòng)運(yùn)行,并且會(huì)對(duì) search.jsp 進(jìn)行編譯并顯示給用戶。如 圖10 所示。
圖10:用戶界面
- 在輸入框中輸入關(guān)鍵詞 “information” 然后單擊 Search 按鈕。然后這個(gè)頁面上會(huì)顯示出搜索結(jié)果來,如 圖11 所示。
圖11:搜索結(jié)果
- 單擊搜索結(jié)果的第一個(gè)鏈接,頁面上就會(huì)顯示出所鏈接到的頁面的內(nèi)容。如 圖12 所示.
圖12:詳細(xì)信息
現(xiàn)在我們已經(jīng)成功的完成了示例項(xiàng)目的開發(fā),并成功的用Lucene實(shí)現(xiàn)了搜索和索引功能。你可以下載這個(gè)項(xiàng)目的源代碼。
總結(jié)
Lucene 提供了靈活的接口使我們更加方便的設(shè)計(jì)我們的 Web 搜索應(yīng)用程序。如果你想在你的應(yīng)用程序中加入搜索功能,那么 Lucene 是一個(gè)很好的選擇。在設(shè)計(jì)你的下一個(gè)帶有搜索功能的應(yīng)用程序的時(shí)候可以考慮使用 Lucene 來提供搜索功能。
下載
描述 |
名字 |
大小 |
下載方法 |
Lucene Web 應(yīng)用程序示例 |
wa-lucene2_source_code.zip |
504KB |
HTTP
|
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1215172