一、概述及坏境搭配
日常开发中,相信大家经常会用like去匹配一些数据,同时我们也知道,like往往会导致全表扫描,当数据量越来越大的时候,我们会纠结于数据库的龟速查找,此时我们必须另寻蹊跷,这时lucene就可以大显身手了。
Lunene具体是什么在这里我就不概述了,有兴趣的同学可以google;而在我们项目中,我们可以根据Lucene用在数据查询,或者在web应用里完成爬虫检索等工作,在这里我主要提供一个抛砖引玉的作用,希望大家可以灵活应用Lucene。
首先,我们需要下载Lunene组件。这里我下载的是Lunene.net。
当我们下载好组件后,解压,然后放进项目的根目录里,之后在启动项目里引用相关的dll;OK,现在前提工作完美完成,接下来我们进入具体的项目中。
二、正常like查询
首先我们看一下当数据库里存在10w条数据时,通过like查询所用匹配“流行”所用的时间,差不多79秒;
三、Lucene查询
现在写一个通过Lucene搜索的demo。
[csharp] view plaincopyprint?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Lucene.Net.Index;
using Lucene.Net.Store;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Documents;
using System.Data;
using System.Diagnostics;
using Lucene.Net.Search;
using Lucene.Net.QueryParsers;
namespace lucene.Controllers
{
public class HomeController : Controller
{
static string path = @"D:Sample";
static void Main(string[] args)
{
//创建索引
CreateIndex();
var watch = Stopwatch.StartNew();
//搜索;两种构建IndexSearcher对象的方法:Directory对象与文件路径.(前者是推荐的)
IndexSearcher search = new IndexSearcher(path);
//查询表达式,QueryParset把查询表达式转换成Lucene内置的查询类型
QueryParser query = new QueryParser(string.Empty, new StandardAnalyzer());
//使用Hits对象访问搜索结果;用来获取匹配结果的一个指针,优点类似C#中的延迟加载,目的都是一样,提高性能;(query.parse:注入查询条件)
/*
* Length() Hits对象集合中所包含的文档的数量
* Document(n) 排名第n的Document实例
* Id(n) 排名第n的DocumentID
* Score(n) 排名第n的标准分值
*/
/*//对搜索结果的再处理,一般使用BooleanQuery组合更多的搜索条件来达成效果
Filter filter = new DateFilter(FieldDate, DateTime.Parse("2005-10-1"), DateTime.Parse("2005-10-30"));
var hits = search.Search(query.Parse("Content:流行"),filter);*/
var hits = search.Search(query.Parse("Content:流行"));
for (int i = 0; i < hits.Length(); i++)
{
Console.WriteLine("当前内容:{0}", hits.Doc(i).Get("Content").Substring(0, 20) + "...");
}
search.Close();
watch.Stop();
Console.WriteLine("搜索耗费时间:{0}", watch.ElapsedMilliseconds);
}
static void CreateIndex()
{
var directory = FSDirectory.GetDirectory(path, true);//从指定目录打开或创建已有索引库
/* 将索引库载入内存,可以提高搜索速度,实现如下。
* private Directory directory = new RAMDirectory(FSDirectory.GetDirectory(@"c:\index", false));
//或
* private Directory directory = new RAMDirectory(c:\index");
* 注意 FSDirectory.GetDirectory 的 create 参数,为 true 时将删除已有索引库文件,可以通过 IndexReader.IndexExists() 方法判断。
*/
//索引的核心方法,创建一个索引,采用StandardAnalyzer对句子进行分词;将 create 参数设为 false,即可往现有索引库添加新数据。
IndexWriter indexWriter = new IndexWriter(directory, new StandardAnalyzer(), false);
var reader = DbHelperSQL.ExecuteReader("select * from News");
while (reader.Read())
{
//域的集合:文档,类似于表的行:对要检索的数据换转成文本、并分析文本、且将分析过的文本保存到索引库中
Document doc = new Document();
//要索引的字段
doc.Add(new Field("ID", reader["ID"].ToString(), Field.Store.YES, Field.Index.TOKENIZED));
doc.Add(new Field("Title", reader["Title"].ToString(), Field.Store.NO, Field.Index.UN_TOKENIZED));
doc.Add(new Field("Content", reader["Content"].ToString(), Field.Store.YES, Field.Index.ANALYZED));
/*如果你想把纯文本文件索引起来,而不想自己将它们读入字符串创建field,你可以用下面的代码创建:
doc.Add(new Field("content", new FileReader(file)));
*/
indexWriter.AddDocument(doc);
}
reader.Close();
/*
* 批量向 FSDirectory 增加索引时,增大合并因子(mergeFactor )和最小文档合并数(minMergeDocs)有助于提高性能,减少索引时间。
* IndexWriter indexWriter = new IndexWriter(directory, analyzer, true);
* indexWriter.maxFieldLength = 1000; // 字段最大长度
indexWriter.mergeFactor = 1000;
indexWriter.minMergeDocs = 1000;
for (int i = 0; i < 10000; i )
{ // Add Documentes... }
indexWriter.Optimize();
*/
//对索引文件进行优化,优化过程会降低索引的效率,优化结果提高搜索性能。不要时时Optimize(),优化一次就够了
indexWriter.Optimize();
indexWriter.Close();
}
}
}
448ms,虽然这个时间是不包含"创建索引“的时间,但是从时间复杂度上来说,这种预加载索引已算是常量……毫无疑问,完败78707ms;
四、lucene的相关知识点
1.我们可以给 Document 和 Field 增加权重(Boost),使其在搜索结果排名更加靠前。
缺省情况下,搜索结果以 Document.Score 作为排序依据,该数值越大排名越靠前。Boost 缺省值为 1。
Score = Score * Boost
通过上面的公式,我们就可以设置不同的权重来影响排名。
如银行或者其它某些有关会员优先的行业中根据 VIP 级别设定不同的权重;
[csharp] view plaincopyprint?
Document document = new Document(); switch (vip)
{
case VIP.Gold: document.SetBoost(2F); break;
case VIP.Argentine: document.SetBoost(1.5F); break;
}
只要 Boost 足够大,那么就可以让某个命中结果永远排第一位,也就是百度等网站的"收费排名"业务。
2.查询之后,使用Sort对象排序
通过 SortField 的构造参数,我们可以设置排序字段,排序条件,以及倒排。
[csharp] view plaincopyprint?
Sort sort = new Sort(new SortField(FieldName, SortField.DOC, false));
IndexSearcher searcher = new IndexSearcher(reader);
Hits hits = searcher.Search(query, sort);
如果需要按照索引顺序(索引时的文档ID)排序,则使用Sort.INDEXORDER作为参数
排序对搜索速度影响还是很大的,尽可能不要使用多个排序条件。(建议采用默认的积分排序,设计良好的加权机制)
3.组合搜索
除了使用 QueryParser.Parse 分解复杂的搜索语法外,还可以通过组合多个 Query(Query子类) 来达到目的。
[csharp] view plaincopyprint?
Query query1 = new TermQuery(new Term(FieldValue, "name1")); //词语搜索
Query query2 = new WildcardQuery(new Term(FieldName, "name*")); //通配符
Query query3 = new PrefixQuery(new Term(FieldName, "name1")); //字段搜索Field:Keyword,自动在结尾添加 *
Query query4 = new RangeQuery(new Term(FieldNumber, NumberTools.LongToString(11L)), new Term(FieldNumber, NumberTools.LongToString(13L)), true); //范围搜索
Query query5 = new FilteredQuery(query, filter); //带过滤条件的搜索
Query query6 =new MatchAllDocsQuery(... // 用来匹配所有文档
Query query7 = new FuzzyQuery (...模糊搜索
Query query8 = new RegexQuery (.. 正则搜索
Query query9 = new SpanRegexQuery(...)。 同上, 正则表达式的查询:
Query query9 = new SpanQuery 的子类嵌套其他SpanQuery 增加了 rewrite方法
Query query10 =new DisjunctionMaxQuery () ..类,提供了针对某个短语的最大score。这一点对多字段的搜索非常有用
Query query11 = new ConstantScoreQuery 类它包装了一个 filter produces a score
BooleanQuery query = new BooleanQuery();
query.Add(query1, BooleanClause.Occur.MUST);
query.Add(query2, BooleanClause.Occur.SHOULD);
//这个是为了联合多个查询而做的Query类. BooleanQuery增加了最小的匹配短语。见:BooleanQuery.setMinimumNumberShouldMatch().
IndexSearcher searcher = new IndexSearcher(reader);
Hits hits = searcher.Search(query);
如果你想查找某个距离内的数据,超过这个距离的不予考虑。比如说你想找"美国"和"高中"间不超过5个字距的句子,你可以:
[csharp] view plaincopyprint?
PhraseQuery query 13= new PhraseQuery();
query.setSlop(5);
query.add(new Term("content ", “美国”));
query.add(new Term(“content”, “高中”));
4.分布搜索
我们可以使用 MultiReader 或 MultiSearcher 搜索多个索引库。
[csharp] view plaincopyprint?
MultiReader reader = new MultiReader(new IndexReader[] { IndexReader.Open(@"c:\index"), IndexReader.Open(@"\\server\index") });
IndexSearcher searcher = new IndexSearcher(reader);
Hits hits = searcher.Search(query);
或
IndexSearcher searcher1 = new IndexSearcher(reader1);
IndexSearcher searcher2 = new IndexSearcher(reader2);
MultiSearcher searcher = new MultiSearcher(new Searchable[] { searcher1, searcher2 });
Hits hits = searcher.Search(query);
还可以使用 ParallelMultiSearcher 进行多线程并行搜索。
5.显示搜索语法字符串
我们组合了很多种搜索条件,或许想看看与其对等的搜索语法串是什么样的。
[csharp] view plaincopyprint?
BooleanQuery query = new BooleanQuery(); query.Add(query1, true, false);
query.Add(query2, true, false); //...
Console.WriteLine("Syntax: {0}", query.ToString());
输出:
Syntax: +(name:name* value:name*) +number:[0000000000000000b TO 0000000000000000d]