一、解析搜索请求
搜索请求的概念是,用户输入关键词,然后程序去分析关键词,获取用户搜索的真实意图。
Lucene提供了一套QueryParser类,用来解析搜索请求。这个类是可以使用的。
1、QueryParser的基本使用
QueryParser用来分析用户输入的关键词,将关键词转换成Query对象。其构造方法如下所示:
QueryParser parser = new QueryParser(Lucene.Net.Util.Version.LUCENE_30, "title", new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_30)); Query q = parser.Parse("飞"); Console.WriteLine(q); //title:飞
这里,QueryParser解析成了:title:飞。
2、使用QueryParser解析多个关键词
如果将字符串“love mother”传递给搜索引擎,搜索引擎将如何去搜索?
它可以执行如下操作:
- 搜索到所有含有"love"的记录以及所有含有"mother"的记录,然后将两部分记录加在一起,这是逻辑或的关系。
- 搜索到所有含有"love"的记录以及所有含有"mother"的记录,然后取它们的公共部分,这是逻辑与的关系。
示例:
QueryParser parser = new QueryParser(Lucene.Net.Util.Version.LUCENE_30, "title", new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_30)); Query q = parser.Parse("love mother"); Console.WriteLine(q); //title:love title:mother
从结果来看,Lucene把输入的关键词按照逻辑或的关系进行了处理。
QueryParser解析的结果是: title:love title:mother 意思是,在“title”字段搜索“love”,在“title”字段搜索“mother”,两部分结果加到一起。我们可以修改这种逻辑,使其按照逻辑与的关系进行处理,也就是取出两个关键词的搜索结果的公共部分。
QueryParser parser = new QueryParser(Lucene.Net.Util.Version.LUCENE_30, "title", new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_30)); parser.DefaultOperator = Lucene.Net.QueryParsers.QueryParser.Operator.AND; Query q = parser.Parse("love mother"); Console.WriteLine(q); //+title:love +title:mother
Lucene引擎对关键词使用了逻辑与运算,解析结果为:
+title:love +title:mother
意思是,在“title”字段搜索“love”,在“title”字段搜索“mother”,两部分结果取公共部分。
QueryParser类还有其他的一些用法,比如进行通配符识别和范围搜索识别等。但是,不建议使用QueryParser类来做搜索请求解析的工作,最好永远不要用这个类。对搜索请求的处理应该在搜索引擎程序前面一层的应用程序中去做,我们在这一层将用户输入的搜索关键词做好了解析之后,交给Query类去封装。
二、高级搜索
高级搜索的内容包括:
- 多字段搜索;
- 多索引搜索;
- 多线程搜索;
1、多字段搜索
一个文档中含有“标题”,“正文”等字段,搜索一个关键词,不管它在标题中出现还是在正文中出现都算符合条件,这就是多字段搜索。
1、利用BooleanQuery实现多字段搜索
要实现多字段搜索很容易,利用前面学到的BooleanQuery就可以做到。首先设置两个TermQuery,然后在它们之间做逻辑运算即可。如下面的示例实现,title:张且body:打。
//建立索引 using (IndexWriter writer = new IndexWriter(ramdirectory, analyzer, maxFieldLength)) { Document document1 = new Document(); document1.Add(new Field("title", "张飞牛人", Field.Store.YES, Field.Index.ANALYZED)); document1.Add(new Field("body", "张飞是一个牛人", Field.Store.YES, Field.Index.ANALYZED)); writer.AddDocument(document1); Document document2 = new Document(); document2.Add(new Field("title", "张飞打架", Field.Store.YES, Field.Index.ANALYZED)); document2.Add(new Field("body", "张飞很能打", Field.Store.YES, Field.Index.ANALYZED)); writer.AddDocument(document2); } //查找 using (IndexSearcher searcher = new IndexSearcher(ramdirectory)) { Term t1 = new Term("title", "张"); TermQuery q1 = new TermQuery(t1); Term t2 = new Term("body", "打"); 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("title")); } }
2、利用MultiFieldQueryParser实现多字段搜索
除了通过使用BooleanQuery实现多字段搜索之外,Lucene还专门提供了一个MultiFieldQuery Parser类,用来实现多字段搜索。其实,只不过是个封装,用起来简单,内部还是用BooleanQuery实现的。
其构造方法如下:
MultiFieldQueryParser(Lucene.Net.Util.Version matchVersion, string[] fields, Analyzer analyzer)
第一个参数是多个字段名称组成的数组,第二个参数是分析器对象实例。
string[] fields = { "title","body" }; MultiFieldQueryParser mp = new MultiFieldQueryParser(Lucene.Net.Util.Version.LUCENE_30, fields, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_30)); Query q = mp.Parse("打");
以上是“或”关系。
2、多索引搜索
为了减少单个索引目录的大小,时常将索引放在许多目录中,这些索引的结构都是一致的。
比如有一个城市的网站搜索引擎,随着时间的增长,我们可能会将索引的目录按照年份分成2003、2004、2005等。旧的索引目录被搜索的几率小,所以将其单独分出去,这样,可以减小新的索引目录,加快搜索速度。
但是有些时候,必须实现多个索引的同时搜索,因为我们需要存放在这些索引中的信息。要实现多索引搜索,只需要对每个索引目录都用IndexSearcher搜索一遍,最后将搜索结果合并起来。
Lucene中专门提供了MultiSearcher类来实现多索引搜索。使用MultiSearcher类实现多索引搜索的方法如下:
static void Main(string[] args) { Analyzer analyzer = new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_30); IndexWriter.MaxFieldLength maxFieldLength = new IndexWriter.MaxFieldLength(10000); DirectoryInfo dir = new DirectoryInfo(@"D:\1"); Lucene.Net.Store.Directory directory1 = new SimpleFSDirectory(dir); DirectoryInfo dir2 = new DirectoryInfo(@"D:\2"); Lucene.Net.Store.Directory directory2 = new SimpleFSDirectory(dir2); //建立索引 using (IndexWriter writer = new IndexWriter(directory1, analyzer, maxFieldLength)) { Document document1 = new Document(); document1.Add(new Field("title", "张飞很能打", Field.Store.YES, Field.Index.ANALYZED)); writer.AddDocument(document1); } //建立索引 using (IndexWriter writer = new IndexWriter(directory2, analyzer, maxFieldLength)) { Document document2 = new Document(); document2.Add(new Field("title", "关羽也很能打", Field.Store.YES, Field.Index.ANALYZED)); writer.AddDocument(document2); } IndexSearcher searcher1 = new IndexSearcher(directory1); IndexSearcher searcher2 = new IndexSearcher(directory2); IndexSearcher[] searchers = { searcher1, searcher2 }; //查找 using (MultiSearcher MySearcher = new MultiSearcher(searchers)) { Term t = new Term("title","打"); Query query = new TermQuery(t); TopDocs docs = MySearcher.Search(query, null, 10); for (int i = 0; i < docs.TotalHits; i++) { Document doc = MySearcher.Doc(docs.ScoreDocs[i].Doc); //输出 张飞打架 或关系 Console.WriteLine(doc.Get("title")); } } Console.ReadKey(); }
最后释放搜索器资源的时候,只需要关闭MultiSearcher的实例,在关闭它的时候,相应的几个IndexSearcher也都被关闭了。
即调用了MySearch.Dispose();相当于调用了searcher1.Dispose();和searcher2.Dispose();
3、多线程搜索
MultiSearcher的实现机制是对IndexSearcher数组中的每个IndexSearcher对象执行搜索,得到的搜索结果之后合并到一起。在其中一个IndexSearcher执行搜索时,其他的IndexSearcher处于等待状态,因此,依然是一个目录一个目录地搜索,一个目录被搜索,其他目录在队列中等待。
可以改良这种搜索方式,就是用多线程。同时检索多个索引目录。Lucene提供了ParallelMultiSearcher来实现多线程搜索。这个类是MultiSearcher的直接子类。其使用方法与MultiSearcher一致(一模一样)。
IndexSearcher searcher1 = new IndexSearcher(directory1); IndexSearcher searcher2 = new IndexSearcher(directory2); IndexSearcher[] searchers = { searcher1, searcher2 }; //查找 using (ParallelMultiSearcher MySearcher = new ParallelMultiSearcher(searchers))
使用方式与MultiSearcher一模一样,因此多余的代码不贴了。注意,这种方式在各个索引目录都比较大的情况下,效率提高会比较明显。