执行搜索 《第三篇》
一、执行搜索的基本方式
使用Lucene.net执行搜索,首先要创建IndexSearcher对象,然后通过Term和Query对象来封装用户输入的搜索条件,最后将结果封装在Hits对象中,返回给用户。
1、创建搜索器对象:IndexSearcher
创建IndexSearcher对象的方法:如下:
IndexSearcher searcher = new IndexSearcher(索引目录实例);
创建IndexSearcher对象很容易,创建完成之后,就可以使用它进行搜索了。它最常用的方式是search(),使用search方法将返回一个结果集对象,即Hits。
2、封装搜索条件:使用Term和Query对象
例如我们要从一个索引中搜索"title"字段包含“中国”的文档。即用户的搜索条件是:字段为title,关键词为“中国”。这时:
Term t = new Term("title","中国");
然后,我们要创建一个Query对象,从而把Term对象转化为可执行的查询条件。Query对象有很多种,我们这里介绍最简单的TermQuery对象。用法如下:
Query query = new TermQuery(t);
3、执行搜索
用户的搜索请求被封装好了之后,就该把请求传递给IndexSearcher对象,使其执行搜索。IndexSearcher对象调用search方法,以Query对象为参数,返回搜索结果。在3.0.3版后返回TopDocs对象。
TopDocs docs = searcher.Search(query, null, 1000);
查找的完整过程如下:
//查找 using (IndexSearcher searcher = new IndexSearcher(fsdirectory)) { Term t = new Term("Name", "备"); Query query = new TermQuery(t); TopDocs docs = searcher.Search(query, null, 1000); Document doc = searcher.Doc(docs.ScoreDocs[0].Doc); Console.WriteLine(doc.Get("Name")); }
二、搜索请求的构建和解析
在前面,我们学到了使用TermQuery构建最基本的搜索,也就是普通的关键词搜索。
现在来学习如何构建复杂的搜索请求,例如,要求搜索结果同时包含多个关键词或者要求搜索结果满足某一个正则表达式等。
在Lucene中,使用Query对象来封装搜索请求。Query类本身是一个抽象类,所以,实际起作用的是其子类。其不同的子类(如TermQuery、BooleanQuery等)分别用来构建不同的搜索请求。
1、词条搜索
词条搜索就是TermQuery,TermQuery的构建方法是,首先构建Term,然后以Term为参数构建TermQuery:
Term t = new Term(要搜索的Field, 搜索关键词); Query query = new TermQuery(t);
2、组合搜索
在实际的搜索中,我们往往需要搜索的结果中既包含“A”又包含“B”,或者含有“A”不含有“B”,也就是说,我们的搜索关键词之间有一些逻辑关系。
在Lucene中,使用BooleanQuery对象来封装组合搜索请求。
1、包含A且包含B
BooleanQuery的构建方式是,首先构建多个TermQuery,然后以多个TermQuery为参数构建BooleanQuery,在多个Term之间要进行逻辑运算。
//建立索引 using (IndexWriter writer = new IndexWriter(ramdirectory, analyzer, maxFieldLength)) { Document document1 = new Document(); document1.Add(new Field("Name", "张飞", Field.Store.YES, Field.Index.ANALYZED)); writer.AddDocument(document1); Document document2 = new Document(); document2.Add(new Field("Name", "大飞", Field.Store.YES, Field.Index.ANALYZED)); writer.AddDocument(document2); Document document3 = new Document(); document3.Add(new Field("Name", "小张", Field.Store.YES, Field.Index.ANALYZED)); writer.AddDocument(document3); } //查找 using (IndexSearcher searcher = new IndexSearcher(ramdirectory)) { Term t1 = new Term("Name", "飞"); Query q1 = new TermQuery(t1); Term t2 = new Term("Name", "张"); TermQuery q2 = new TermQuery(t2); BooleanQuery q = new BooleanQuery(); q.Add(q1, Occur.MUST); q.Add(q2, Occur.MUST); TopDocs docs = searcher.Search(q, null, 10); for (int i = 0; i < docs.TotalHits; i++) { Document doc = searcher.Doc(docs.ScoreDocs[i].Doc); Console.WriteLine(doc.Get("Name")); } }
以上代码示例中,首先建立了两个TermQuery,第一个TermQuery是要求Name字段里含有“飞”,第二个TermQuery是要求Name字段里有“张”。
然后建立BooleanQuery对象。使用BooleanQuery对象的Add方法将TermQuery对象添加进来。第一个参数是TermQuery对象,第二个参数是该TermQuery对象的逻辑表达式,对于这个参数,有三个取值:
- Occur.MUST:表示搜索结果必须满足该Term;
- Occur.MUST_NOT:表示搜索结果不需满足该Term;
- Occur.SHOULD:表示搜索结果可以满足该Term;
利用这三个值,可以在许多TermQuery之间构造多种逻辑关系。
- 与:MUST+MUST;
- 非:MUST_NOT;
- 或:SHOULD;
2、包含A且不包含B
再比如,满足一些条件,又不满足一些条件的示例:
Term t1 = new Term("Name", "飞"); Query q1 = new TermQuery(t1); Term t2 = new Term("Name", "张"); TermQuery q2 = new TermQuery(t2); BooleanQuery q = new BooleanQuery(); q.Add(q1, Occur.MUST); q.Add(q2, Occur.MUST_NOT); //有飞但不含有张 输出大飞
3、不包含A且不包含B
要求搜索出不含有某些条件的一切结果,这在逻辑上是成立的,需要多个MUST_NOT组合。但是,这种搜索请求实际上相当于用户没有输入关键词。而是输入了过滤词,对于实际的搜索引擎来讲,这种搜索方式会返回数量巨大的结果,严重消耗服务器资源。所以,Lucene实现的BooleanQuery并不支持这种类型的搜索,当单纯将多个MUST_NOT组合在一起时,搜索结果是空。
4、含有A或含有B
要求搜索结果含有某个关键词或其他关键词。这是要在多个关键词之间构建逻辑“或”的关系,需要使用SHOULD组合。
Term t1 = new Term("Name", "大"); Query q1 = new TermQuery(t1); Term t2 = new Term("Name", "小"); TermQuery q2 = new TermQuery(t2); BooleanQuery q = new BooleanQuery(); q.Add(q1, Occur.SHOULD); //有大或有小 q.Add(q2, Occur.SHOULD); //输出大飞 小张
5、补充说明
BooleanQuery对象所支持的子查询数量默认情况下组多是1024个。可以通过静态属性MaxClauseCount修改这个默认值。当然,子查询数量越少,查询速度就越快。
3、范围搜索
范围搜索,就是要求搜索结果处于某个范围之内。原本的RangeQuery由于效率太低,已经很少使用,现在常用的方式有如下两种:
- TermRangeQuery;
- NumericRangeQuery;
范围搜索有如下两种方式:
1、TermRangeQuery
//建立索引 using (IndexWriter writer = new IndexWriter(ramdirectory, analyzer, maxFieldLength)) { Document document1 = new Document(); document1.Add(new Field("Age", "17", Field.Store.YES,Field.Index.NOT_ANALYZED)); writer.AddDocument(document1); Document document2 = new Document(); document2.Add(new Field("Age", "18", Field.Store.YES, Field.Index.NOT_ANALYZED)); writer.AddDocument(document2); Document document3 = new Document(); document3.Add(new Field("Age", "19", Field.Store.YES, Field.Index.NOT_ANALYZED)); writer.AddDocument(document3); } //查找 using (IndexSearcher searcher = new IndexSearcher(ramdirectory)) { TermRangeQuery q = new TermRangeQuery("Age", "0", "18", true, true); TopDocs docs = searcher.Search(q, null, 10); for (int i = 0; i < docs.TotalHits; i++) { Document doc = searcher.Doc(docs.ScoreDocs[i].Doc); Console.WriteLine(doc.Get("Age")); } }
2、NumericRangeQuery
使用NumericRangeQuery的方式,在添加索引的时候要使用NumericField添加数字索引。
Lucene.net 3.0.3 支持如下类型的范围检索:
- Double;
- Float;
- Int;
- Long;
NumericField的构造方法如下:
NumericField(Field名称, 有效数字, 是否存储, 是否会用于搜索);
另外,NumericRangeQuery通过其静态方法创建:
NumericRangeQuery.NewIntRange(要检索的字段名, 起始范围, 结束范围, 是否包含左边界, 是否包含右边界);
示例:
//建立索引 using (IndexWriter writer = new IndexWriter(ramdirectory, analyzer, maxFieldLength)) { Document document1 = new Document(); NumericField f1 = new NumericField("Age", Field.Store.YES, true); f1.SetIntValue(17); document1.Add(f1); writer.AddDocument(document1); Document document2 = new Document(); NumericField f2 = new NumericField("Age", Field.Store.YES, true); f2.SetIntValue(18); document2.Add(f2); writer.AddDocument(document2); Document document3 = new Document(); NumericField f3 = new NumericField("Age", Field.Store.YES, true); f3.SetIntValue(19); document3.Add(f3); writer.AddDocument(document3); } //查找 using (IndexSearcher searcher = new IndexSearcher(ramdirectory)) { NumericRangeQuery<int> query = NumericRangeQuery.NewIntRange("Age", 0, 18, true, true); TopDocs docs = searcher.Search(query, null, 10); for (int i = 0; i < docs.TotalHits; i++) { Document doc = searcher.Doc(docs.ScoreDocs[i].Doc); Console.WriteLine(doc.Get("Age")); } }
4、前缀搜索
前缀搜索使用PrefixQuery来实现,用于搜索以某词为起始的字段。其构造方法如下:
PrefixQuery(Term prefix);
搜索示例:
Term t = new Term("Name","d"); PrefixQuery query = new PrefixQuery(t);
就目前来说,好像只支持英文,中文好像还不支持,待确定。
5、短语搜索
Lucene提供了PhraseQuery可以把一些短语组合起来,形成新的短语。但要注意这种搜索类型中可以设置匹配度。
例如,我们设置“刘”,“华”。如果设置间隔量为1则可以匹配“刘德华”,如果设置间隔量为2,则可以匹配“刘德德华”。
代码示例:
//建立索引 using (IndexWriter writer = new IndexWriter(ramdirectory, analyzer, maxFieldLength)) { Document document1 = new Document(); document1.Add(new Field("title", "智利西北部遭8级强震,引发2米高海啸", Field.Store.YES, Field.Index.ANALYZED)); writer.AddDocument(document1); Document document2 = new Document(); document2.Add(new Field("title", "震中附近城市居民1/4有华人血统", Field.Store.YES, Field.Index.ANALYZED)); writer.AddDocument(document2); Document document3 = new Document(); document3.Add(new Field("title", "联想按PC套路做智能电视会一败涂地吗", Field.Store.YES, Field.Index.ANALYZED)); writer.AddDocument(document3); } //查找 using (IndexSearcher searcher = new IndexSearcher(ramdirectory)) { Term t1 = new Term("title", "附"); Term t2 = new Term("title", "城"); PhraseQuery query = new PhraseQuery(); query.Slop = 1; //设置间隔的字符数量 query.Add(t1); query.Add(t2); TopDocs docs = searcher.Search(query, null, 10); for (int i = 0; i < docs.TotalHits; i++) { Document doc = searcher.Doc(docs.ScoreDocs[i].Doc); //满足条件的为第二条 Console.WriteLine(doc.Get("title")); } }
如果不设置间隔量的话,则直接匹配“附城”。
6、多短语搜索
多短语搜索类就是MultiPhraseQuery类,相当于首先指定一个前缀,然后把其他词语加在它后面,从而组成词语。
例如,指定“飞”作为统一前缀,然后指定“鸟”,“机”作为后缀,Lucene会自动组成“飞鸟”,“飞机”两个词进行搜索。
示例:
//建立索引 using (IndexWriter writer = new IndexWriter(ramdirectory, analyzer, maxFieldLength)) { Document document1 = new Document(); document1.Add(new Field("title", "飞阿斯顿机,", Field.Store.YES, Field.Index.ANALYZED)); writer.AddDocument(document1); Document document2 = new Document(); document2.Add(new Field("title", "飞呀飞啊,小飞机", Field.Store.YES, Field.Index.ANALYZED)); writer.AddDocument(document2); Document document3 = new Document(); document3.Add(new Field("title", "飞呀飞啊大飞鸟", Field.Store.YES, Field.Index.ANALYZED)); writer.AddDocument(document3); } //查找 using (IndexSearcher searcher = new IndexSearcher(ramdirectory)) { Term t1 = new Term("title", "飞"); Term t2 = new Term("title", "机"); Term t3 = new Term("title", "鸟"); MultiPhraseQuery query = new MultiPhraseQuery(); query.Slop = 3; //这个设置依然生效,如果启用这行代码,则3条记录都出来 query.Add(t1); query.Add(new Term[] { t2, t3 }); TopDocs docs = searcher.Search(query, null, 10); for (int i = 0; i < docs.TotalHits; i++) { Document doc = searcher.Doc(docs.ScoreDocs[i].Doc); Console.WriteLine(doc.Get("title")); } }
7、模糊搜索
模糊搜索就是FuzzyQuery类,用于洋文搜索。如果两个词的词形相似,那么可以设定匹配度,从而搜索出结果。
在创建FuzzyQuery的时候,有个重载构造方法可以输入一个float类型的模糊度。这个值越小,搜索的模糊性越强,搜索出来的结果越多。
比如,"good"与"god"不一样,搜索"good"和搜索"god"会得到不同的结果。但是,如果使用了FuzzyQuery类,就可以使得搜索引擎把用户输入的"good"当做"god"去处理。
//建立索引 using (IndexWriter writer = new IndexWriter(ramdirectory, analyzer, maxFieldLength)) { Document document1 = new Document(); document1.Add(new Field("title", "god", Field.Store.YES, Field.Index.ANALYZED)); writer.AddDocument(document1); Document document2 = new Document(); document2.Add(new Field("title", "good", Field.Store.YES, Field.Index.ANALYZED)); writer.AddDocument(document2); Document document3 = new Document(); document3.Add(new Field("title", "ggg", Field.Store.YES, Field.Index.ANALYZED)); writer.AddDocument(document3); } //查找 using (IndexSearcher searcher = new IndexSearcher(ramdirectory)) { Term t1 = new Term("title", "god"); FuzzyQuery query = new FuzzyQuery(t1,0.5f); TopDocs docs = searcher.Search(query, null, 10); //得到 god与good for (int i = 0; i < docs.TotalHits; i++) { Document doc = searcher.Doc(docs.ScoreDocs[i].Doc); Console.WriteLine(doc.Get("title")); } }
8、通配符搜索
通配符,通常用“*”表示任意多个字符,用“?”表示任意一个字符。由此,“飞*”可以匹配“飞鸟”,“飞机”,“飞天”等等。
Lucene使用WildcardQuery来实现通配符搜索。
Term t = new Term("title", "飞*"); WildcardQuery query = new WildcardQuery(t);
像这种搜索,如果是经过分词的结果,那么没有什么意义,相当于直接普通搜索一个“飞”,通常用于搜索没经过分词的Field。如果没经过分词的,上面的搜索,相当于搜索长度是2,而且以飞开始的Field。