转载,文章来源: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( 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();
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)
构造函数:
主要方法有:
Add() | 增加索引 |
Get() | 获取索引字段存储的值 |
GetBoost() | 获取权重 |
SetBoost() | 设置权重 |
GetField() | 返回赋予字段的名 |
RemoveField() | 从文档中删除特定名的字段 |
三.字段(域)Fields
构造函数:
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是非常有用的,比如产品数量 |
四.搜索(示例)
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,引入该插件,我们可以是搜索结果高亮显示。
示例:
//其它代码省略
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资源。