Lucene应用编程接口(API)功能强大、非常灵活、易于使用。Lucene不但提供了出众的全文搜索功能,还提供了所有补充性的过滤和排序特性。如果想把高性能、特性丰富的多重标准全文搜索机制添加到应用程序中,就需要这些功能和特性。
索引
任何Lucene应用程序的第一步就是为数据建立索引。Lucene需要使用数据创建自己的一组索引,以便它可以对数据进行高性能的全文检查、过滤和排序等操作。这是相当简单、直观的过程。首先,需要创建IndexWriter对象,可以使用该对象建立Lucene索引,并把它写到磁盘上。Lucene非常灵活,它有许多选项。这里,我们只是在“index”目录里面建立简单的索引结构:
Directory directory = FSDirectory.getDirectory("index", true);
Analyzer analyser = new StandardAnalyzer();
IndexWriter writer = new IndexWriter(directory, analyser, true);
接下来,需要为数据记录建立索引。需要为每个记录建立单独的索引。用Lucene为记录建立索引时,要为每个记录创建“文档”(Document)对象。要让全文索引发挥作用,就要为Lucene提供可以建立索引的一些数据。最简单的选项就是编写一个方法,写入记录的全文描述(包括想要搜索的各项内容),然后使用这个值作为可搜索字段。这里,我们把这个字段称为“description”。可以通过为文档添加“字段”(Field)类的新实例,来为字段建立索引,如下所示:
Field field = new Field("field", value, Field.Store.NO, Field.Index.TOKENIZED);
doc.add(field);
可以选择指定自己是否想保存该值供将来使用(Field.Store.YES),还是只是为它建立索引(Field.Store.NO)。后一个选项适用于想建立索引、但以后不想检索的大值。第四个参数表明想要如何为值建立索引。如果使用Field.Index.TOKENIZED,值就会被分析,让Lucene可以更充分地利用功能强大的全文索引和搜索特性。正如我们会看到的那样,缺点在于,无法按标记化(tokenized)的字段对结果进行排序。如果想为字段建立索引,而不需要先进行分析,那么Field.Index.UN_TOKENIZED很有用。如果只是想保存值,供将来使用,那么可以使用Field.Index.NO。下列代码表明了如何为来自库目录的条目列表建立索引:
List< Item> items = Catalog.getAllItems();
for(Item item : items) {}
Document doc = new Document();
String description = item.getTitle+ " " + item.getAuthors()+ " " + item.getSummary()...;
doc.add(new Field("description", description, Field.Store.NO, Field.Index.TOKENIZED));
... }
上述方法非常适用于全文搜索,但有时候也需要按特定字段进行更加准确的搜索。可搜索字段应当是标记化的,不过它们确实不需要保存起来(除非想直接从Lucene文档获得字段值)。设想一下:如果需要根据库目录建立全文索引,目录里面有成千上万个条目,譬如图书、文章、报纸、视频和声音等资料。下列代码说明了如何按特定库条目(这里是图书)的书名和国际标准图书编号添加可搜索的索引:
doc.add(new Field("title", item.getTitle(), Field.Store.NO, Field.Index.TOKENIZED));
doc.add(new Field("isbn", item.getISBNNumber(), Field.Store.NO, Field.Index.TOKENIZED));
doc.add(new Field("type", Item.BOOK, Field.Store.NO, Field.Index.TOKENIZED));
writer.addDocument(doc);
...
writer.close();
有时候往往会需要在表中显示搜索结果,让用户可以按列对结果进行排序。这可以用Lucene来完成,不过有一个问题:字段必须是UN_TOKENIZED。这意味着,无法对可搜索的索引进行排序:需要添加有着不同名字的另一个索引。办法之一就是用某种易于识读的方式为字段名添加前缀,如下所示:
// 按照书名字段的可搜索索引
doc.add(new Field("sort-on-title", book.getTitle(), Field.Store.YES, Field.Index.UN_TOKENIZED));
// 按照国际标准图书编号字段的可搜索索引
doc.add(new Field("sort-on-isbn", book.getISBNNumber(), Field.Store.YES, Field.Index.UN_TOKENIZED));
全文搜索
Lucene的全文搜索比较容易实现。典型的Lucene全文搜索如下所示:
Searcher is = indexer.getIndexSearcher();
QueryParser parser = indexer.getQueryParser("description");
Query query = parser.parse("Some full-text search terms");
Hits hits = is.search(query);
这里,我们使用索引程序按“描述”(description)字段执行全文搜索。Lucene返回“搜索结果”(Hits)对象,我们可以使用该对象获得匹配文档,如下所示:
for (int i = 0; i < searchResults.length(); i++) {
Document doc = searchResults.doc(i);
String title = (String) doc.getField("title");
System.out.println(title);}
对这部分代码进行扩充以实现多重标准搜索需要多一些工作量。我们这里使用的关键字类是“过滤器”(Filter)类,顾名思义,这个类可以对搜索结果进行过滤。“过滤器”类实际上是一个抽象类。有几种类型的过滤器类可以定义准确的过滤操作。QueryFilter类可以根据Lucene查询表达式来对搜索结果进行过滤。这里,我们构建了一个过滤器,把搜索结果限制为图书,使用了“类型”(type)字段:
Query booksQuery = new TermQuery(new Term("type",Item.BOOK));
Filter typeFilter = new QueryFilter(booksQuery);
RangeFilter类可以把搜索结果限制为某个范围的值。下列过滤器就把搜索结果限制为日期从1990年到1999年的条目,使用了year字段(最后两个布尔字段表明限制值是不是包括在内):
Filter rangeFilter = new RangeFilter("year", "1990", "1999", true, true);
ChainedFilter类可以使用“与”(AND)、“或”(OR)、“异”(XOR)或者“与非”(ANDNOT)等逻辑操作符来合并其他过滤器。在下一个示例中,我们把搜索结果限制为只有与上述两个条件都匹配的文档:
List< Filter> filters = new ArrayList< Filter>();
filters.add(typeFilter);
filters.add(rangeFilter);
Filter filter = new ChainedFilter(filterList, ChainedFilter.AND);
可以把同一操作符应用于所有过滤器,也可以提供一组操作符,这样就可以提供不同的操作符供每个过滤器使用。不过,应当认真考虑用于多重标准搜索的操作符。譬如说,在典型的多重标准搜索中,可能会让用户使用复选框(图书、文章和视频等),从而选择他们需要的文档类型。来自这些复选框值的过滤器通常需要使用“或”(OR)表达式来进行合并。 另一方面,酒店预订网站可能会提供房间号、类别或者酒店位置等搜索标准。这些是限制性的标准,它们需要使用“与”(AND)表达式来进行合并。
以下是一个比较完整的示例,它使用了我们上面讨论过的所有特性:
public List< CatalogItem> search(String expression, boolean displayBooks, boolean displayArticles, boolean displayVideo) {
List< Filter> filters = new ArrayList< Filter>();
//显示图书
if (displayBooks) {
Query booksQuery = new TermQuery(new Term("type",Item.BOOK));
filters.add(new QueryFilter(booksQuery)); }
// 显示文章
if (displayArticles) {
Query articlesQuery = new TermQuery(new Term("type",Item.ARTICLE));
filters.add(new QueryFilter(articlesQuery)); }
// 显示录像
if (displayVideo) {
Query videoQuery = new TermQuery(new Term("type",Item.VIDEO));
filters.add(new QueryFilter(videoQuery)); }
Filter filter = new ChainedFilter(filterList, ChainedFilter.OR);
QueryParser parser = indexer.getQueryParser("description");
Query query = parser.parse(expression);
hits = is.search(query, filter);
... }
结果排序
对搜索结果进行排序是用户对Web应用程序的一个常见需求。如今JavaServer Faces和Tapestry等许多基于组件的Web框架拥有表组件,让用户可以对每一列进行排序,就像Struts这些较为传统的模型-视图-控制器框架那样。一旦返回了搜索结果,就有可能在内存中对它们进行排序。不过,这种方法浪费严重,而且效率低下。无论是传统的关系数据库应用程序,还是Lucene应用程序,在源处执行排序操作要有效得多。
正如我们在前面看到的那样,Lucene可以建立专门用来对结果进行排序的索引。可以只对这些字段执行排序操作,因为对关系数据库中的未索引字段进行排序是不明智的。要使用这些字段,就要使用“排序”(Sort)类。使用这个类的最简单的方法就是,只要创建一个新实例,并提供想要进行排序的列。然后把这个“排序”(Sort)实例传递给search()方法,如下所示:
Sort sort = new Sort("name");
hits = is.search(query, filter, sort);
除了这个简单示例外,Lucene还提供了一系列广泛的排序功能。只要利用列名字来指定布尔标记,就可以进行逆序排序。这里,我们按名字进行递减排序:
Sort sort = new Sort("name", true);
也可以通过提供一组列名字,对几个列进行排序:
String[] sortOrder = {"lastName","firstName"};
Sort sort = new Sort(sortOrder);
如果需要按每个字段使用不同的排序顺序,就要使用“字段排序”(SortField)类。这里,我们按姓进行递增排序,然后按出生日期朝廷递减排序:
SortField([] sortOrder = {new SortField("lastName"),new SortField("dateOfBirth",true)};
Sort sort = new Sort(sortOrder);
(计算机世界报 2006年12月04日 第47期 B33)

浙公网安备 33010602011771号