转载,文章来源:http://blog.csdn.net/akunshenjk/archive/2008/03/28/2226040.aspx

 

     早在一年前我就曾接触到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资源。

posted on 2008-08-21 03:16  EasyWriter  阅读(1299)  评论(0编辑  收藏  举报