??xml version="1.0" encoding="utf-8" standalone="yes"?> U别Q?中 ?L (zhoudengpeng@yahoo.com.cn), 软g工程? 上v交通大?br />
2006 q?9 ?06 ?/p>
在本文章中Q你?x)学习(fn)到如何利?Lucene 实现高搜烦(ch)功能以及(qing)如何利用 Lucene 来创?Web 搜烦(ch)应用E序。通过q些学习(fn)Q你可以利?Lucene 来创q搜烦(ch)应用E序?/p>
通常一?Web 搜烦(ch)引擎的架构分为前端和后端两部分,像图一中所C。在前端程中,用户在搜索引擎提供的界面中输入要搜烦(ch)的关键词Q这里提到的用户界面一般是一个带有输入框?Web 面Q然后应用程序将搜烦(ch)的关键词解析成搜索引擎可以理解的形式Qƈ在烦(ch)引文件上q行搜烦(ch)操作。在排序后,搜烦(ch)引擎q回搜烦(ch)l果l用戗在后端程中,|络爬虫或者机器h从因特网上获?Web 面Q然后烦(ch)引子pȝ解析q些 Web 面q存入烦(ch)引文件中。如果你惛_?Lucene 来创Z?Web 搜烦(ch)应用E序Q那么它的架构也和上面所描述的类|如图一中所C?/p>
Lucene 支持多种形式的高U搜索,我们在这一部分中会(x)q行探讨Q然后我?x)?Lucene ?API 来演C如何实现这些高U搜索功能?/p>
大多数的搜烦(ch)引擎都会(x)提供布尔操作W让用户可以l合查询Q典型的布尔操作W有 AND, OR, NOT。Lucene 支持 5 U布?yu)(dng)操作符Q分别是 AND, OR, NOT, ?+), ?-)。接下来我会(x)讲述每个操作W的用法? 接下来我们看一下如何利?Lucene 提供?API 来实现布?yu)(dng)查询?a cmimpressionsent="1">清单1 昄?jin)如果利用布(yu)(dng)操作符q行查询的过E?/p>
Lucene 支持域搜索,你可以指定一ơ查询是在哪些域(Field)上进行。例如,如果索引的文档包含两个域Q?code>Title ? Lucene 支持两种通配W:(x)问号Q?Q和星号Q?Q。你可以使用问号Q?Q来q行单字W的通配W查询,或者利用星P*Q进行多字符的通配W查询。例如,如果你想搜烦(ch) tiny 或?tonyQ你可以用查询语?“t?ny”Q如果你x(chng)?Teach, Teacher ?TeachingQ你可以用查询语?“Teach*”?a cmimpressionsent="1">清单3 昄?jin)通配W查询的q程? Lucene 提供的模p查询基于编辑距ȝ?Edit distance algorithm)。你可以在搜索词的尾部加上字W?~ 来进行模p查询。例如,查询语句 “think~” q回所有包含和 think cM的关键词的文档?a cmimpressionsent="1">清单 4 昄?jin)如果利?Lucene ?API q行模糊查询的代码? 范围搜烦(ch)匚w某个域上的值在一定范围的文档。例如,查询 “age:[18 TO 35]” q回所?age 域上的值在 18 ?35 之间的文档?a cmimpressionsent="1">清单5昄?jin)利?Lucene ?API q行q回搜烦(ch)的过E? 接下来我们开发一?Web 应用E序利用 Lucene 来检索存攑֜文g服务器上?HTML 文档。在开始之前,需要准备如下环境:(x) q个例子使用 Eclipse q行 Web 应用E序的开发,最l这?Web 应用E序跑在 Tomcat 5.0 上面。在准备好开发所必需的环境之后,我们接下来进?Web 应用E序的开发? 在我们的设计中,把该pȝ分成如下四个子系l:(x) ? 昄?jin)我们设计的详细信息Q我们将用户接口子系l放?webContent 目录下面。你?x)看C个名?search.jsp 的页面在q个文g多w面。请求管理子pȝ在包 在分析了(jin)pȝ的架构设计之后,我们接下来看pȝ实现的详l信息? q个JSP的第二部分负责显C搜索结果给用户Q如?所C:(x) ?a cmimpressionsent="1">清单6中, ?a cmimpressionsent="1">清单7中,注意到在q个c里面有三个U有属性。第一个是 在类 q个cd含两个私有属性,分别? 现在我们来看一下放在包 现在我们可以?Tomcat 5.0 上运行开发好的应用程序? 现在我们已经成功的完成了(jin)CZ目的开发,q成功的用Lucene实现?jin)搜索和索引功能。你可以下蝲q个目的源代码Q?a cmimpressionsent="1">下蝲Q? Lucene 提供?jin)灵zȝ接口使我们更加方便的设计我们?Web 搜烦(ch)应用E序。如果你惛_你的应用E序中加入搜索功能,那么 Lucene 是一个很好的选择。在设计你的下一个带有搜索功能的应用E序的时候可以考虑使用 Lucene 来提供搜索功能?/p>
Lucene 是基?Java 的全文信息检索包Q它目前?Apache Jakarta 家族下面的一个开源项目。在q篇文章中,我们首先来看如何利用 Lucene 实现高搜烦(ch)功能Q然后学?fn)如何利?Lucene 来创Z个健壮的 Web 搜烦(ch)应用E序?/blockquote>
Figure 1. Web 搜烦(ch)引擎架构
清单1Q用布?yu)(dng)操作?/strong>
//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]);
}
}
Content
Q你可以用查?“Title: Lucene AND Content: Java” 来返回所有在 Title 域上包含 Lucene q且?Content 域上包含 Java 的文档?a cmimpressionsent="1">清单 2 昄?jin)如何利?Lucene ?API 来实现域搜烦(ch)?
清单2Q实现域搜烦(ch)
//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);
}
清单3Q进行通配W查?/strong>
//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]);
}
}
清单4Q实现模p查?/strong>
//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]);
}
}
清单5Q测试范围搜?/strong>
//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");
}
回页?/strong>
图二Q创建动态Web目
图三Q动?Web 目的结?/strong>
sample.dw.paper.lucene.servlet
下面Q类 SearchController
负责功能的实现。搜索子pȝ攑֜?sample.dw.paper.lucene.search
当中Q它包含?jin)两个类Q?code>SearchManager ?SearchResultBean
Q第一个类用来实现搜烦(ch)功能Q第二个cȝ来描q搜索结果的l构。烦(ch)引子pȝ攑֜?sample.dw.paper.lucene.index
当中。类 IndexManager
负责?HTML 文g创徏索引。该子系l利用包 sample.dw.paper.lucene.util
里面的类 HTMLDocParser
提供的方?getTitle
?getContent
来对 HTML 面q行解析?
囑֛Q项目的架构设计
?Q向Web服务器提交搜索请?/strong>
?Q显C搜索结?/strong>
SearchController
?servlet 用来实现该子pȝ?a cmimpressionsent="1">清单Q?/a>l出?jin)这个类的源代码?
清单Q:(x)h理器的实现
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);
}
}
doPost
Ҏ(gu)从客L(fng)获取搜烦(ch)词ƈ创徏c?SearchManager
的一个实例,其中c?SearchManager
在搜索子pȝ中进行了(jin)定义。然后,SearchManager
的方?search ?x)被调用。最后搜索结果被q回到客L(fng)?
SearchResultBean
。第一个类用来实现搜烦(ch)功能Q第二个cL个JavaBeanQ用来描q搜索结果的l构?a cmimpressionsent="1">清单7l出?jin)?SearchManager
的源代码?
清单7Q搜索功能的实现
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;
}
}
searchWord
Q代表了(jin)来自客户端的搜烦(ch)词。第二个?indexManager
Q代表了(jin)在烦(ch)引子pȝ中定义的c?IndexManager
的一个实例。第三个?analyzer
Q代表了(jin)用来解析搜烦(ch)词的解析器。现在我们把注意力放在方?search
上面。这个方法首先检查烦(ch)引文件是否已l存在,如果已经存在Q那么就在已l存在的索引上进行检索,如果不存在,那么首先调用c?IndexManager
提供的方法来创徏索引Q然后在新创建的索引上进行检索。搜索结果返回后Q这个方法从搜烦(ch)l果中提取出需要的属性ƈ为每个搜索结果生成类 SearchResultBean
的一个实例。最后这?SearchResultBean
的实例被攑ֈ一个列表里面ƈq回l请求管理器?/p>
SearchResultBean
中,含有两个属性,分别?htmlPath
?htmlTitle
Q以?qing)这个两个属性的 get ?set Ҏ(gu)。这也意味着我们的搜索结果包含两个属性:(x)htmlPath
?htmlTitle
Q其?htmlPath
代表?HTML 文g的\径,htmlTitle
代表?HTML 文g的标题?
IndexManager
用来实现q个子系l?a cmimpressionsent="1">清单8 l出?jin)这个类的源代码?
清单8Q烦(ch)引子pȝ的实?/strong>
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;
}
}
dataDir
?indexDir
?code>dataDir 代表存放{待q行索引?HTML 面的\径,indexDir
代表?jin)存?Lucene 索引文g的\径。类 IndexManager
提供?jin)三个方法,分别?createIndex
, addDocument
?ifIndexExist
。如果烦(ch)引不存在的话Q你可以使用Ҏ(gu) createIndex
dZ个新的烦(ch)引,用方?addDocument
d一个烦(ch)引上d文档。在我们的场景中Q一个文档就是一?HTML 面。方?addDocument
?x)调用由c?HTMLDocParser
提供的方法对 HTML 文档q行解析。你可以使用最后一个方?ifIndexExist
来判?Lucene 的烦(ch)引是否已l存在?sample.dw.paper.lucene.util
里面的类 HTMLDocParser
。这个类用来?HTML 文g中提取出文本信息。这个类包含三个Ҏ(gu)Q分别是 getContent
Q?code>getTitle ?getPath
。第一个方法返回去除了(jin) HTML 标记的文本内容,W二个方法返?HTML 文g的标题,最后一个方法返?HTML 文g的\径?a cmimpressionsent="1">清单9 l出?jin)这个类的源代码?
清单9QHTML 解析?/strong>
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;
}
}
?Q配|?Tomcat 5.0
?Q选择 Tomcat 5.0
?Q完成Tomcat 5.0的配|?/strong>
?0Q用L(fng)?/strong>
?1Q搜索结?/strong>
?2Q详l信?/strong>
回页?/strong>
?周登 (zhoudengpeng@yahoo.com.cn), 软g工程?br />
2006 q?4 ?20 ?/p>
本文首先介绍?jin)Lucene的一些基本概念,然后开发了(jin)一个应用程序演CZ(jin)利用Lucene建立索引q在该烦(ch)引上q行搜烦(ch)的过E?/blockquote>Lucene 是一个基?Java 的全文信息检索工具包Q它不是一个完整的搜烦(ch)应用E序Q而是Z的应用程序提供烦(ch)引和搜烦(ch)功能。Lucene 目前?Apache Jakarta 家族中的一个开源项目。也是目前最为流行的Z Java 开源全文检索工具包?/p>
目前已经有很多应用程序的搜烦(ch)功能是基?Lucene 的,比如 Eclipse 的帮助系l的搜烦(ch)功能。Lucene 能够为文本类型的数据建立索引Q所以你只要能把你要索引的数据格式{化的文本的,Lucene p对你的文档进行烦(ch)引和搜烦(ch)。比如你要对一?HTML 文档QPDF 文档q行索引的话你就首先需要把 HTML 文档?PDF 文档转化成文本格式的Q然后将转化后的内容交给 Lucene q行索引Q然后把创徏好的索引文g保存到磁盘或者内存(sh)Q最后根据用戯入的查询条g在烦(ch)引文件上q行查询。不指定要烦(ch)引的文档的格式也?Lucene 能够几乎适用于所有的搜烦(ch)应用E序?/p>
?1 表示?jin)搜索应用程序?Lucene 之间的关p,也反映了(jin)利用 Lucene 构徏搜烦(ch)应用E序的流E:(x)
?. 搜烦(ch)应用E序?Lucene 之间的关p?/strong>
![]()
回页?/strong>
索引是现代搜索引擎的核心(j)Q徏立烦(ch)引的q程是把源数据处理成非常方便查询的索引文g的过E。ؓ(f)什么烦(ch)引这么重要呢Q试想你现在要在大量的文档中搜烦(ch)含有某个关键词的文档Q那么如果不建立索引的话你就需要把q些文档序的读入内存,然后(g)查这个文章中是不是含有要查找的关键词Q这L(fng)话就?x)耗费非常多的旉Q想x(chng)索引擎可是在毫秒U的旉内查扑և要搜索的l果的。这是׃建立?jin)?ch)引的原因Q你可以把烦(ch)引想象成q样一U数据结构,他能够你快速的随机讉K存储在烦(ch)引中的关键词Q进而找到该关键词所兌的文档。Lucene 采用的是一U称为反向烦(ch)引(inverted indexQ的机制。反向烦(ch)引就是说我们l护?jin)一个词/短语表,对于q个表中的每个词/短语Q都有一个链表描qC(jin)有哪些文档包含了(jin)q个?短语。这样在用户输入查询条g的时候,p非常快的得到搜烦(ch)l果。我们将在本pd文章的第二部分详l介l?Lucene 的烦(ch)引机Ӟ׃ Lucene 提供?jin)简单易用的 APIQ所以即使读者刚开始对全文本进行烦(ch)引的机制q不太了(jin)解,也可以非常容易的使用 Lucene 对你的文档实现烦(ch)引?/p>
Ҏ(gu)档徏立好索引后,可以在q些索引上面q行搜烦(ch)?jin)。搜索引擎首先会(x)Ҏ(gu)索的关键词进行解析,然后再在建立好的索引上面q行查找Q最l返回和用户输入的关键词相关联的文档?/p>
回页?/strong>
Lucene 软g包的发布形式是一?JAR 文gQ下面我们分析一下这?JAR 文g里面的主要的 JAVA 包,使读者对之有个初步的?jin)解?/p>
Package: org.apache.lucene.document
q个包提供了(jin)一些ؓ(f)装要烦(ch)引的文档所需要的c,比如 Document, Field。这P每一个文档最l被装成了(jin)一?Document 对象?/p>
Package: org.apache.lucene.analysis
q个包主要功能是Ҏ(gu)档进行分词,因ؓ(f)文档在徏立烦(ch)引之前必要q行分词Q所以这个包的作用可以看成是为徏立烦(ch)引做准备工作?/p>
Package: org.apache.lucene.index
q个包提供了(jin)一些类来协助创建烦(ch)引以?qing)对创徏好的索引q行更新。这里面有两个基的类QIndexWriter ?IndexReaderQ其?IndexWriter 是用来创建烦(ch)引ƈd文档到烦(ch)引中的,IndexReader 是用来删除烦(ch)引中的文档的?/p>
Package: org.apache.lucene.search
q个包提供了(jin)对在建立好的索引上进行搜索所需要的cR比?IndexSearcher ?Hits, IndexSearcher 定义?jin)在指定的?ch)引上q行搜烦(ch)的方法,Hits 用来保存搜烦(ch)得到的结果?/p>
回页?/strong>
假设我们的电(sh)脑的目录中含有很多文本文档,我们需要查扑֓些文档含有某个关键词。ؓ(f)?jin)实现这U功能,我们首先利用 Lucene 对这个目录中的文档徏立烦(ch)引,然后在徏立好的烦(ch)引中搜烦(ch)我们所要查扄文档。通过q个例子读者会(x)对如何利?Lucene 构徏自己的搜索应用程序有个比较清楚的认识?/p>
回页?/strong>
Z(jin)Ҏ(gu)档进行烦(ch)引,Lucene 提供?jin)五个基的类Q他们分别是 Document, Field, IndexWriter, Analyzer, Directory。下面我们分别介l一下这五个cȝ用途:(x)
Document
Document 是用来描q文档的Q这里的文档可以指一?HTML 面Q一电(sh)子邮Ӟ或者是一个文本文件。一?Document 对象由多?Field 对象l成的。可以把一?Document 对象惌成数据库中的一个记录,而每?Field 对象是记录的一个字Dc(din)?/p>
Field
Field 对象是用来描qC个文档的某个属性的Q比如一电(sh)子邮件的标题和内容可以用两个 Field 对象分别描述?/p>
Analyzer
在一个文档被索引之前Q首先需要对文档内容q行分词处理Q这部分工作是?Analyzer 来做的。Analyzer cL一个抽象类Q它有多个实现。针对不同的语言和应用需要选择适合?Analyzer。Analyzer 把分词后的内容交l?IndexWriter 来徏立烦(ch)引?/p>
IndexWriter
IndexWriter ?Lucene 用来创徏索引的一个核?j)的c,他的作用是把一个个?Document 对象加到索引中来?/p>
Directory
q个cM表了(jin) Lucene 的烦(ch)引的存储的位|,q是一个抽象类Q它目前有两个实玎ͼW一个是 FSDirectoryQ它表示一个存储在文gpȝ中的索引的位|。第二个?RAMDirectoryQ它表示一个存储在内存当中的烦(ch)引的位置?/p>
熟?zhn)了(jin)徏立?ch)引所需要的q些cdQ我们就开始对某个目录下面的文本文件徏立烦(ch)引了(jin)Q清?l出?jin)对某个目录下的文本文g建立索引的源代码?/p>
清单 1. Ҏ(gu)本文件徏立烦(ch)?/strong>
package TestLucene; import java.io.File; import java.io.FileReader; import java.io.Reader; import java.util.Date; 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; /** * This class demonstrate the process of creating index with Lucene * for text files */ public class TxtFileIndexer { public static void main(String[] args) throws Exception{ //indexDir is the directory that hosts Lucene's index files File indexDir = new File("D:\\luceneIndex"); //dataDir is the directory that hosts the text files that to be indexed File dataDir = new File("D:\\luceneData"); Analyzer luceneAnalyzer = new StandardAnalyzer(); File[] dataFiles = dataDir.listFiles(); IndexWriter indexWriter = new IndexWriter(indexDir,luceneAnalyzer,true); long startTime = new Date().getTime(); for(int i = 0; i < dataFiles.length; i++){ if(dataFiles[i].isFile() && dataFiles[i].getName().endsWith(".txt")){ System.out.println("Indexing file " + dataFiles[i].getCanonicalPath()); Document document = new Document(); Reader txtReader = new FileReader(dataFiles[i]); document.add(Field.Text("path",dataFiles[i].getCanonicalPath())); document.add(Field.Text("contents",txtReader)); indexWriter.addDocument(document); } } indexWriter.optimize(); indexWriter.close(); long endTime = new Date().getTime(); System.out.println("It takes " + (endTime - startTime) + " milliseconds to create index for the files in directory " + dataDir.getPath()); } }
在清?中,我们注意到类 IndexWriter 的构造函数需要三个参敎ͼW一个参数指定了(jin)所创徏的烦(ch)引要存放的位|,他可以是一?File 对象Q也可以是一?FSDirectory 对象或?RAMDirectory 对象。第二个参数指定?Analyzer cȝ一个实玎ͼ也就是指定这个烦(ch)引是用哪个分词器Ҏ(gu)挡内容进行分词。第三个参数是一个布?yu)(dng)型的变量,如果?true 的话׃表创Z个新的烦(ch)引,?false 的话׃表在原来索引的基上进行操作。接着E序遍历?jin)目录下面的所有文本文档,qؓ(f)每一个文本文档创Z(jin)一?Document 对象。然后把文本文档的两个属性:(x)路径和内容加入到?jin)两?Field 对象中,接着在把q两?Field 对象加入?Document 对象中,最后把q个文档?IndexWriter cȝ add Ҏ(gu)加入到烦(ch)引中厅R这h们便完成?jin)?ch)引的创徏。接下来我们q入在徏立好的烦(ch)引上q行搜烦(ch)的部分?/p>
回页?/strong>
利用Luceneq行搜烦(ch)像建立索引一样也是非常方便的。在上面一部分中,我们已经Z个目录下的文本文档徏立好?jin)?ch)引,现在我们p在这个烦(ch)引上q行搜烦(ch)以找到包含某个关键词或短语的文档。Lucene提供?jin)几个基的类来完成这个过E,它们分别是呢IndexSearcher, Term, Query, TermQuery, Hits. 下面我们分别介绍q几个类的功能?/p>
Query
q是一个抽象类Q他有多个实玎ͼ比如TermQuery, BooleanQuery, PrefixQuery. q个cȝ目的是把用户输入的查询字W串装成Lucene能够识别的Query?/p>
Term
Term是搜索的基本单位Q一个Term对象有两个Stringcd的域l成。生成一个Term对象可以有如下一条语句来完成QTerm term = new Term(“fieldName”,”queryWord”); 其中W一个参C表了(jin)要在文档的哪一个Field上进行查找,W二个参C表了(jin)要查询的关键词?/p>
TermQuery
TermQuery是抽象类Query的一个子c,它同时也是Lucene支持的最为基本的一个查询类。生成一个TermQuery对象由如下语句完成:(x) TermQuery termQuery = new TermQuery(new Term(“fieldName”,”queryWord”)); 它的构造函数只接受一个参敎ͼ那就是一个Term对象?/p>
IndexSearcher
IndexSearcher是用来在建立好的索引上进行搜索的。它只能以只ȝ方式打开一个烦(ch)引,所以可以有多个IndexSearcher的实例在一个烦(ch)引上q行操作?/p>
Hits
Hits是用来保存搜索的l果的?/p>
介绍完这些搜索所必须的类之后Q我们就开始在之前所建立的烦(ch)引上q行搜烦(ch)?jin),清?l出?jin)完成搜索功能所需要的代码?/p>
清单2 Q在建立好的索引上进行搜?/strong>
package TestLucene; import java.io.File; import org.apache.lucene.document.Document; import org.apache.lucene.index.Term; import org.apache.lucene.search.Hits; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.TermQuery; import org.apache.lucene.store.FSDirectory; /** * This class is used to demonstrate the * process of searching on an existing * Lucene index * */ public class TxtFileSearcher { public static void main(String[] args) throws Exception{ String queryStr = "lucene"; //This is the directory that hosts the Lucene index File indexDir = new File("D:\\luceneIndex"); FSDirectory directory = FSDirectory.getDirectory(indexDir,false); IndexSearcher searcher = new IndexSearcher(directory); if(!indexDir.exists()){ System.out.println("The Lucene index is not exist"); return; } Term term = new Term("contents",queryStr.toLowerCase()); TermQuery luceneQuery = new TermQuery(term); Hits hits = searcher.search(luceneQuery); for(int i = 0; i < hits.length(); i++){ Document document = hits.doc(i); System.out.println("File: " + document.get("path")); } } }
在清?中,cIndexSearcher的构造函数接受一个类型ؓ(f)Directory的对象,Directory是一个抽象类Q它目前有两个子c:(x)FSDirctory和RAMDirectory. 我们的程序中传入?jin)一个FSDirctory对象作ؓ(f)其参敎ͼ代表?jin)一个存储在盘?sh)的索引的位|。构造函数执行完成后Q代表了(jin)q个IndexSearcher以只ȝ方式打开?jin)一个烦(ch)引。然后我们程序构造了(jin)一个Term对象Q通过q个Term对象Q我们指定了(jin)要在文档的内容中搜烦(ch)包含关键?#8221;lucene”的文档。接着利用q个Term对象构造出TermQuery对象q把q个TermQuery对象传入到IndexSearcher的searchҎ(gu)中进行查询,q回的结果保存在Hits对象中。最后我们用?jin)一个@环语句把搜烦(ch)到的文档的\径都打印?jin)出来。好?jin),我们的搜索应用程序已l开发完毕,怎么P利用Lucene开发搜索应用程序是不是很简单?/p>
回页?/strong>
本文首先介绍?Lucene 的一些基本概念,然后开发了(jin)一个应用程序演CZ(jin)利用 Lucene 建立索引q在该烦(ch)引上q行搜烦(ch)的过E。希望本文能够ؓ(f)学习(fn) Lucene 的读者提供帮助?/p>
周登朋,软g工程师,上v交通大学研I生Q对 Java 技术以?qing)信息检索技术很感兴。?zhn)可以通过 zhoudengpeng@yahoo.com.cn 与他联系?/p>
]]> վ֩ģ壺 | Ѯ| ̩| ֶ| | | | ϰ| | | | ɽ| ״| | | DZɽ| ƾ| ƽ| μ| | ʯ| ˴| ʡ| | Ԫı| | | | Ƿ| | ԣ| | г| | | ¡| Դ| | | °Ͷ| Ԫ|