Lucene.net 高数据检索量时怎样显著提高查找效率(从无到有学习Lucene)

一、概述及坏境搭配

日常开发中,相信大家经常会用like去匹配一些数据,同时我们也知道,like往往会导致全表扫描,当数据量越来越大的时候,我们会纠结于数据库的龟速查找,此时我们必须另寻蹊跷,这时lucene就可以大显身手了。


Lucene具体是什么在这里我就不概述了,有兴趣的同学可以google;而在我们项目中,我们可以根据Lucene用在数据查询,或者在web应用里完成爬虫检索等工作,在这里我主要提供一个抛砖引玉的作用,希望大家可以灵活应用Lucene。

首先,我们需要下载Lucene组件。这里我下载的是Lcnene.net。我已上传,需要的同学可以去我的上传空间里下载。

地址:http://download.csdn.net/detail/jintougao/6398027

当我们下载好组件后,解压,然后放进项目的根目录里,之后在启动项目里引用相关的dll;OK,现在前提工作完美完成,接下来我们进入具体的项目中。

二、正常like查询

首先我们看一下当数据库里存在10w条数据时,通过like查询所用匹配“流行”所用的时间,差不多79秒;


三、Lucene查询

现在写一个通过Lucene搜索的demo。

 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 级别设定不同的权重;

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 的构造参数,我们可以设置排序字段,排序条件,以及倒排。

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子类) 来达到目的。

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个字距的句子,你可以:

PhraseQuery query 13= new PhraseQuery();
query.setSlop(5);
query.add(new Term("content ", “美国”));
query.add(new Term(“content”, “高中”));

4.分布搜索

我们可以使用 MultiReader 或 MultiSearcher 搜索多个索引库。

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.显示搜索语法字符串

我们组合了很多种搜索条件,或许想看看与其对等的搜索语法串是什么样的。

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]


参考文档:www.cnblogs.com/JoinZhang/archive/2010/08/25/1808131.html

没有更多推荐了,返回首页