基于Lucene.net开源搜索 浅谈

        早在一年前我就曾接触到Lucene.net 当时版本为1.9.1 .004 ;当时学习该框架时,正赶上它宣布进去商业化,可能不会再出更高版本了;后来不知道什么原因,又重新开源,并出了新版本(2.0.0.004),也是一年前的事了。刚接触Lucene,net的目的是改善网站的搜索,提高搜索效率。由于lucene是有java开源框架迁移过来的,所以相关资料大多是java的文档,相关文档基本是英文,所以当时学习的时候是比较吃力的。最后经过两个星期的奋斗,总算是把lucenet.net的脾性摸清楚了,并且做出来了相应的程序。可是,用了不多一个星期,发现写的程序并没有提升搜索效率,很是失望;同事决定弃用lucenet.net,换用其它方式。

          最近网站的数据量越来越大,搜索的效率再次成了大问题。于是又回到Lucene.net上,这次当然要用新版本(2.0.0.004),结果其与1.9.1.004版本有一些差别。先将这次学习经验盘点如下:

一.创建索引:IndexWriter 

     其构造函数:

public  IndexWriter(    string  path,   Analyzer a,    bool  create);
public  IndexWriter(   FileInfo path,   Analyzer a,    bool  create);
public  IndexWriter(   Directory d,   Analyzer a,    bool  create);

我们常用的是第一个构造函数。string path 为索引所在目录,如:D:/search/index; Analyzer a是选用词语分析器;如 new StandardAnalyzer();最后一个参数是表示是否需要重新创建,如果是初次创建索引,则用true,否则一般为false。

所以创建索引可以按如下写:创建新的文档:
Document doc = new Document();

  // 索引已存在,则直接打开 否则创建,并打开
//this.m_strIndexPath 为索引目录
IndexWriter m_writer  = new  IndexWriter( this .m_strIndexPath,  new  Lucene.Net.Analysis.Standard.StandardAnalyzer(),  ! System.IO.File.Exists( System.IO.Path.Combine( this .m_strIndexPath,  " segments " )));

二.创建文档(Lucene.Net.Documents)
构造函数:

public  Document();

 

主要方法有:

Add()增加索引
Get()获取索引字段存储的值
GetBoost()获取权重
SetBoost()设置权重
GetField()返回赋予字段的名
RemoveField()从文档中删除特定名的字段

三.字段(域)Fields
构造函数:

public  Field(    string  name,    byte [] value_Renamed,   Store store);
public  Field(    string  name,   TextReader reader);
public  Field(    string  name,   TextReader reader,   TermVector termVector);
public  Field(    string  name,    string  value_Renamed,   Store store,   Index index);
public  Field(    string  name,    string  value_Renamed,   Store store,   Index index,   TermVector termVector);

string name 为字段名, string value_Renamed 字段对应的值,Store 对应有YES 和NO,YES则存储对应值,否则不存储;Field.Index对应有四个值,这对我们创建索引以及优化有很大的帮助.

NO不把字段值编入索引,因此该字段不会被搜索到
NO_NORMS不使用分析器将字段值编入索引,不会按规范存储,它的优点是占用空间少
TOKENIZED 将字段值编入索引,并可以被搜索到,在这个规则被存储在索引之前,有一个分析会被用用来标志这个正文,或者可能的话,使这个正文更加正规化,这对于普通文本是非常有用的。
UN_TOKENIZED 不使用分析器将字段值编入索引,因此它可以被搜索到, 没有使用分析器,值将被存储为单独的项目,这对唯一的ID是非常有用的,比如产品数量

四.搜索(示例)

System.DateTime dt  =  System.DateTime.Now;
IndexSearcher searcher 
=   new  IndexSearcher( @" d:searchprovide " );
string  q = Keyword.Text;
Query query1 
=  QueryParser.Parse(q,  " P_Name " new  StandardAnalyzer());
Hits hits
= searcher.Search(query1);
TimeSpan ts 
=  System.DateTime.Now.Subtract(dt);
string  results  =  ts.TotalMilliseconds.ToString()  +   " 毫秒;Found  "   +  hits.Length()  +   "  document(s) that matched query ' "   +  q  +   " ':<br/> " ;
int  count = hits.Length();
if (count > 30 )count = 30 ;
for  ( int  i  =   0 ; i  <  count; i ++
{
     Document doc 
= hits.Doc(i);
     results
+=doc.Get("HtmlPath")+"<br/>";
     results
+=doc.Get("ID")+"<br/>";
}

Label1.Text
= results;            
searcher.Close();

五,高亮显示查询结果

在Lucene.net2.0里有插件 Highlighter.Net,引入该插件,我们可以是搜索结果高亮显示。

示例:

using  Lucene.Net.Highlight; // 引入
// 其它代码省略
 QueryParser q  =   new  QueryParser( " P_Name " , analyzer);
  Query query1 
=  q.Parse(Request[ " bizKeyword " ]);
 query.Add(query1, BooleanClause.Occur.MUST);
  Lucene.Net.Highlight.Formatter fm 
=   new  SimpleHTMLFormatter( " <span style="color:red;font-weight:bold"> " " </span> " );
  highlighter 
=   new  Highlighter(fm,  new  QueryScorer(query1));
  highlighter.SetTextFragmenter(
new  SimpleFragmenter( 100 ));

// 中间代码省略

// 高亮显示
  string  P_Name  =  doc.Get( " P_Name " );
  Lucene.Net.Analysis.TokenStream tokenStream 
=  analyzer.TokenStream( " P_Name " new  System.IO.StringReader(P_Name));
  P_Name 
=  highlighter.GetBestFragments(tokenStream, P_Name,  0 " ... " );
                               
            

六.索引的实时更新(伪)
         Lucene.net是非常好用的一个开源搜索框架,但是很可惜它不支持读写并发操作。搜索的时候不能更新索引,更新索引的时候不能进行搜索。这对于数据经常要更新的场景,真可谓是致命的。
        在java下有人说:利用compass,hibernate实现lucene索引实时更新。我没试过,相应的资料也没找到。
再说了据我所知,目前Compass还每有从Java迁移到.net,至于hibernate有.net版的 Nhibernate ,不过据说效率很成问题。
        为了解决这问题,我想了很多办法。最后我的解决办法很土,不过我认为还是很好用的。现将我的方法结束如下:
       比如我们要对产品进行建立索引,首先建议一份完整的索引,保存的目录A,再复制一份保存到目录B,A目录的主要作用就是在正常情况下提供搜索的索引目录,B目录则主要用于更新新的索引(包括删除废旧数据,修改,新增数据);一旦更新操作完成,立即通知搜索更换目录到B目录,让删除A里的旧索引,并复制B里的最新索引到A目录。 

这里之所以加一个“伪”字,我这里实现并不是实际意义的实时,这里还是有一定时间差的,因为Lucene.net优化索引比较耗时,我不可能每插入一条数据,就更新索引;我的做法是每间隔一段时间,待修改数据达到一定量时,再一起操作,更新索引,优化。这样也减少了很多I/O操作。

-------------------------------------------------

PS:今天搜索到一篇文章:http://blog.csdn.net/poson/archive/2008/03/21/2201880.aspx

里面简单的说了Lucenet.net的并发操作,如果其正确,那么我的文章里所说的“。搜索的时候不能更新索引,更新索引的时候不能进行搜索”则是错误。尽管如此,但有一点是肯定的:更新索引会耗用大量cpu资源。

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页