转:http://www.zdnet.com.cn/developer/webdevelop/story/0,2000081602,39154640,00.htm
用Lucene来建立一个索引
给你的Web网站加上搜索的功能是增强用户浏览体验的最简单方式之一,但是在你的应用程序里集成一个搜索引擎并不总是很容易。为了帮助你为自己的Java应用程序提供一个灵活的搜索引擎,我会讲解如何使用Lucene,它是一个极其灵活的开放源代码的搜索引擎。
Lucene会直接同你的Web应用程序集成到一起。它是由Jakarta Apache工作组使用Java编写成的。你的Java应用程序能够将Lucene作为任何搜索功能的核心来使用。Lucene能够处理任何类型的文本数据;但是它没有内置对Word、Excel、PDF和XML的支持。但是还是有一些解决方案能够让Lucene支持它们中的每一个。
关于Lucene的重要一点是,它只是一个搜索引擎,因此没有内置Web图形用户界面和Web crawler。要把Lucene集成到你的Web应用程序里,你就要编写一个显示查询表单的servlet或者JSP页面,还要编写另一个列出结果的页面。
用Lucene来建立一个索引
你应用程序的文本内容由Lucene来索引,并被作为一系列索引文件保存在文件系统里。Lucene能够接受代表单篇内容的文档(Document)对象,例如一个Web页面或者PDF文件。你的应用程序就负责将其内容转变成Lucene能够理解的文档对象。
每个文档都是由有一个或者多个的字段(Field)对象。这些字段包含有一个名称和一个值,非常像散裂图里的一个项目(entry)。每个字段都应该对应一段信息,这段信息是同你需要查询或者显示的检索结果相关的。例如,标题应该被用在搜索结果里,因此它会被作为一个字段添加到文档对象里。这些字段可以被索引,也可以不被索引,而原始的数据也可以选择保存在索引里。保存在索引里的字段在创建检索结果页面的时候会很有用。对于搜索没有用处的字段,例如唯一的ID,就不需要被索引,只需要被保存就行了。
字段也可以是标记化了的(tokenized),这就意味着一个分析程序会将输入到字段里的内容分解成搜索引擎能够使用的标记。Lucene带有多个分析程序,但是我只会使用最强大的分析程序——StandardAnalyzer类。
StandardAnalyzer类会将文本的所有内容变成小写的,并去掉一些常用的停顿词(stop word)。停顿词是像“a”、“the”和“in”这样的词,它们都是内容里非常常见的词,但是对搜索却一点用处都没有。分析程序也会分析搜索查询,这就意味着查询会找到匹配的部分。例如,这段文本“The dog is a golden retriever(这条狗是一只金毛猎犬)”,就会被处理为“dog golden retriever”作为索引。当用户搜索“a Golden Dog”的时候,分析程序会处理这个查询,并将其转变为“golden dog”,这就符合我们的内容了。
我们的例子准备使用数据访问对象(Data Access Object,DAO)的商务对象(business object),前者是Java应用程序开发的一个常见模式。我要使用的DAO——ProductDAO见Listing A。
1
Listing A
2
package
com.greenninja.lucene;
3
4
importjava.util.
*
;
5
public
class
ProductDAO
{
6
private
Map map
=
new
HashMap();
7
/** */
/**
8
* Initializes the map with new Products
9
*
10
*/
11
public
void
init()
{
12
13
Product product1
=
new
Product(
"
1E344
"
,
"
Blizzard Convertible
"
,
14
"
The Blizzard is the finest convertible on the market today, with 120 horsepower, 6 seats, and a steering wheel.
"
,
15
"
The Blizzard convertible model is a revolutionary vehicle that looks like a minivan, but has a folding roof like a roadster. We took all of the power from our diesel engines and put it into our all new fuel cell power system.
"
);
16
map.put(product1.getId(),product1);
17
18
Product product2
=
new
Product(
"
R5TS7
"
,
"
Truck 3000
"
,
19
"
Our Truck 3000 model comes in all shapes and sizes, including dump truck, garbage truck, and pickup truck. The garbage truck has a full 3 year warranty.
"
,
20
"
The Truck 3000 is built on the same base as our bulldozers and can be outfitted with an optional hovercraft attachment for all-terrain travel.
"
);
21
map.put(product2.getId(),product2);
22
23
Product product3
=
new
Product(
"
VC456
"
,
"
i954d-b Motorcycle
"
,
24
"
The motorcycle comes with a sidecar on each side, for additional stability and cornering ability.
"
,
25
"
Our motorcycle has the same warranty as our other products and is guaranteed for many miles of fun biking. Each motorcycle is shipped with a nylon windbreaker, goggles, and a helmet with a neat visor.
"
);
26
map.put(product3.getId(),product3);
27
28
}
29
30
/** */
/**
31
* Gets a collection of all of the products
32
*
33
*
@return
all of the products
34
*/
35
public
Collection getAllProducts()
36
{
37
return
map.values();
38
}
39
40
/** */
/**
41
* Gets a product, given the unique id
42
*
43
*
@param
id the unique id
44
*
@return
the Product object, or null if the id wasn't found
45
*/
46
public
Product getProduct(String id)
47
{
48
if
(map.containsKey(id))
49
{
50
return
(Product) map.get(id);
51
}
52
53
//
the product id wasn't found
54
return
null
;
55
}
56
57
}
58
59
为了让这个演示程序简单,我不准备使用数据库,DAO也只会包含产品(Product)对象的一个集合。在本例里,我会采用Listing B
Listing B
package
com.greenninja.lucene;
public
class
Product
{
private
String name;
private
String shortDescription;
private
String longDescription;
private
String id;
/** */
/**
* Constructor to create a new product
*/
public
Product(String i, String n, String sd, String ld)
{
this
.id
=
i;
this
.name
=
n;
this
.shortDescription
=
sd;
this
.longDescription
=
ld;
}
setter
/
getter
}
里的产品对象,并将它们转变成为用于索引的文档。
索引符(Indexer)类在Listing C
Listing C
package
com.greenninja.lucene;
import
java.io.IOException;
import
java.util.Collection;
import
java.util.Iterator;
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;
public
class
Indexer
{
protected
IndexWriter writer
=
null
;
protected
Analyzer analyzer
=
new
StandardAnalyzer();
public
void
init(String indexPath)
throws
IOException
{
//
create a new index every time this is run
writer
=
new
IndexWriter(indexPath, analyzer,
true
);
}
public
void
buildIndex()
throws
IOException
{
//
get the products from the DAO
ProductDAO dao
=
new
ProductDAO();
dao.init();
Collection products
=
dao.getAllProducts();
Iterator iter
=
products.iterator();
while
(iter.hasNext())
{
Product product
=
(Product) iter.next();
//
convert the product to a document.
Document doc
=
new
Document();
//
create an unindexed, untokenized, stored field for the product id
doc.add(Field.UnIndexed(
"
productId
"
,product.getId()));
//
create an indexed, untokenized, stored field for the name
doc.add(Field.Keyword(
"
name
"
,product.getName()));
//
create an indexed, untokenized, stored field for the short description
doc.add(Field.Keyword(
"
short
"
,product.getShortDescription()));
//
create an indexed, tokenized, unstored field for all of the content
String content
=
product.getName()
+
"
"
+
product.getShortDescription()
+
"
"
+
product.getLongDescription();
doc.add(Field.Text(
"
content
"
,content));
//
add the document to the index
try
{
writer.addDocument(doc);
System.out.println(
"
Document
"
+
product.getName()
+
"
added to index.
"
);
}
catch
(IOException e)
{
System.out.println(
"
Error adding document:
"
+
e.getMessage());
}
}
//
optimize the index
writer.optimize();
//
close the index
writer.close();
}
}
里,它将负责把Product转换成为Lucene文档,还负责创建Lucene索引。
产品类里的字段是ID名、简短描述和详细描述。通过使用字段(Field)类的UnIndexed方法,ID会被作为一个非索引的非标记字段被保存。通过使用字段类的Keyword方法,名称和简短描述会被作为索引的非标记字段被保存。搜索引擎会对内容字段进行查询,而内容字段里会包含有产品的名称、简短描述和详细描述字段。
在所有的文档都添加完之后,就要优化索引并关闭索引编写器,这样你才能够使用索引。Lucene的大多数实现都要使用增量索引(incremental indexing),在增量索引里,已经在索引里的文档都是独立更新的,而不是每次先删除索引再创建一个新的。
运行查询
运行查询
创建一个查询并在索引里搜索结果要比创建一个索引简单。你的应用程序会要求使用者提供一个搜索查询,这个查询可以是一个简单的词语。Lucene拥有一些更加高级的查询(Query)类,用于布尔搜索或者整句搜索。
高级查询的一个例子是”Mutual Fund”(互惠基金)AND stock*(股票),它会搜索包含有短语Mutual Fund和以stock开头的词(例如stocks、stock或者甚至是stockings)的文档。
获取更多关于Lucene里查询的信息
Lucene Web网站里的句法页面会提供更加详细的信息。
搜索符(Searcher)类放在Listing D
Listing D
package
com.greenninja.lucene;
import
java.io.IOException;
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;
public
class
Searcher
{
protected
Analyzer analyzer
=
new
StandardAnalyzer();
public
Hits search(String indexPath, String queryString)
throws
IOException, ParseException
{
//
the Lucene index Searcher class, which uses the query on the index
IndexSearcher indexSearcher
=
new
IndexSearcher(indexPath);
//
make the query with our content field, the query string, and the analyzer
Query query
=
QueryParser.parse(queryString,
"
content
"
,analyzer);
Hits hits
=
indexSearcher.search(query);
return
hits;
}
}
里,它负责在Lucene索引里查找你所使用的词语。对于本篇演示程序而言,我使用了一个简单的查询,它只是一个字符串,而没有使用任何高级查询功能。我用QueryParser类从查询字符串里创建了一个查询(Query)对象,QueryParser这个类会使用StandardAnalyzer类将查询字符串分解成标记,再去掉停顿词,然后将这个字符串转换成小写的。
这个查询被传递给一个IndexSearcher对象。IndexSearcher会在索引的文件系统里被初始化。IndexSearcher的搜索方法将接受这个查询并返回一个命中(Hits)对象。这个命中对象包含有作为Lucene文档对象的检索结果,以及结果的长度。使用命中对象的Doc方法将取回命中对象里的每个文档。
文档对象包含有我添加到索引符文档里的字段。这些字段中的一些被保存了,但是没有被标记化,你可以将它们从文档里提取出来。示例应用程序会用搜索引擎运行一个查询,然后显示它所找到的产品名称。
运行演示程序
要运行本文里的示例程序,你需要从Lucene的Web网站下载最新版本的Lucene二进制发布版本(binary distribution)。Lucene发行版的lucene-1.3-rc1.jar文件需要被添加到你Java类的路径下才能够运行这个演示程序。演示程序会在运行com.greenninja.lucene.Demo类的目录下创建一个叫做index的索引目录。你还需要安装好JDK。一行典型的命令是:java -cp c:\java\lucene-1.3-rc1\lucene-1.3-rc1.jar;. com.greenninja.lucene.Demo(见图A)。本例所使用的示例数据包含在ProductDAO类里。这个查询是演示(Demo)类的一部分。
图A
命令行示例
参考资料
· 下载本文相关代码
·javaworld.com:javaworld.com
·Matrix-Java开发者社区:http://www.matrix.org.cn/
·Lucene 搜索引擎库:
http://jakarta.apache.org/lucene/docs/index.html
·MAOS 开源项目:
http://sourceforge.net/projects/maos/