Chinaunix首页 | 论坛 | 博客
  • 博客访问: 225829
  • 博文数量: 75
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 848
  • 用 户 组: 普通用户
  • 注册时间: 2013-10-08 10:27
文章分类
文章存档

2014年(9)

2013年(66)

我的朋友

分类: C#/.net

2013-10-14 18:15:02

一、概述及坏境搭配
日常开发中,相信大家经常会用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]
阅读(2084) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~