Lucene.Net无障碍学习和使用:索引篇

一、简单认识索引

Lucene.Net的应用相对比较简单。一段时间以来,我最多只是在项目中写点代码,利用一下它的类库而已,对很多名词术语不是很清晰,甚至理解可能还有偏差。从我过去的博客你也可以看出,语言表达一直不是个人所长,就算”表达“了也有大面积抄书的嫌疑,所以很多概念性的介绍能省则省(除非特别有别要说明),希望有心的初学者注意,理清概念和辨别技术名词非常重要,请参考相关文档。

Lucene的索引由1或多个segment(片段)构成,一个segment由多个document构成,一个document又由1个或多个field构成,一个field又由一个或多个term构成。下面这张图可以说明一切:

lucenenetindex

从图中不难看出,Lucene的索引是一个由点到线,由线到面的组成结构,这一点我们可以通过查看Lucene生成的索引文件看出来。

参考图片来源: http://alone2004.spaces.live.com/blog/cns!C2525069080D7BB!675.entry

二、创建、优化、删除和更新索引实践

备注:在解决方案所在文件夹中,有一个测试用的Resource文件夹,内有4个.txt文件。我在本地测试的时候,就使用了Resource下的四个文本文件。

1、索引保存至文件

(1)、创建索引

先初始化一个IndexModifier对象,然后执行创建索引的核心方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/// <summary>
/// 给txt文件创建索引
/// </summary>
/// <param name="file"></param>
/// <param name="modifier"></param>
private  void  IndexFile(FileInfo file, IndexModifier modifier)
{
     try
     {
         Document doc = new  Document(); //创建文档,给文档添加字段,并把文档添加到索引书写器里
         SetOutput( "正在建立索引,文件名:"  + file.FullName);
 
         doc.Add( new  Field( "id" , id.ToString(), Field.Store.YES, Field.Index.TOKENIZED)); //存储且索引
         id++;
 
         /* filename begin */
         doc.Add( new  Field( "filename" , file.FullName, Field.Store.YES, Field.Index.TOKENIZED)); //存储且索引
         //doc.Add(new Field("filename", file.FullName, Field.Store.YES, Field.Index.UN_TOKENIZED));
         //doc.Add(new Field("filename", file.FullName, Field.Store.NO, Field.Index.TOKENIZED));
         //doc.Add(new Field("filename", file.FullName, Field.Store.NO, Field.Index.UN_TOKENIZED));
         /* filename end */
 
         /* contents begin */
         //doc.Add(new Field("contents", new StreamReader(file.FullName, System.Text.Encoding.Default)));
 
         string  contents = string .Empty;
         using  (TextReader rdr = new  StreamReader(file.FullName, System.Text.Encoding.Default))
         {
             contents = rdr.ReadToEnd(); //将文件内容提取出来
             doc.Add( new  Field( "contents" , contents, Field.Store.YES, Field.Index.TOKENIZED)); //存储且索引
             //doc.Add(new Field("contents", contents, Field.Store.NO, Field.Index.TOKENIZED));//不存储索引
         }
         /* contents end */
         modifier.AddDocument(doc);
     }
 
     catch  (FileNotFoundException fnfe)
     {
     }
}

最后,IndexModifier对象执行Close方法。

几个注意点:

a、IndexModifier类封装了平时经常使用的IndexWriter和IndexReader,而且不用我们额外考虑多线程;

b、StandardAnalyzer是经常使用的一个Analyzer,目前对中文分词支持的也还不错(大名鼎鼎的盘古分词请参考牛人eaglet的这几篇);

c、IndexModifier的Optimize方法的执行可以优化索引文件,但是比较耗时间,根据我的测试,索引文件越大,优化时间线性增加,所以实际的开发中这个方法我们都会按照一定的策略执行;

d、IndexModifier的Close方法必须执行,否则你所做的一切都是无用功。

(2)、按照id删除一条索引

代码相对而言非常简单,直接利用IndexModifier 的DeleteDocuents方法:

1
2
3
4
5
6
7
Directory directory = FSDirectory.GetDirectory(INDEX_STORE_PATH, false );
IndexModifier modifier = new  IndexModifier(directory, new  StandardAnalyzer(), false );
 
Term term = new  Term( "id" , id);
modifier.DeleteDocuments(term); //删除 
modifier.Close();
directory.Close();

其中,IndexModifier还有一个方法DeleteDocument,它的参数是整数docNum,通常我们也不知道索引文件的内部docNum是多少,所以非常少用它。

(3)、按照id更新一条索引

贴一下主要方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
bool  enableCreate = IsEnableCreated(); //是否已经创建索引文件
Term term = new  Term( "id" , id);
Document doc = new  Document();
doc = new  Document(); //创建文档,给文档添加字段,并把文档添加到索引书写器里
doc.Add( new  Field( "id" , id, Field.Store.YES, Field.Index.TOKENIZED)); //存储且索引
doc.Add( new  Field( "filename" , filename, Field.Store.YES, Field.Index.TOKENIZED));
doc.Add( new  Field( "contents" , filename, Field.Store.YES, Field.Index.TOKENIZED));
LuceneIO.Directory directory = LuceneIO.FSDirectory.GetDirectory(INDEX_STORE_PATH, enableCreate);
IndexWriter writer = new  IndexWriter(directory, new  StandardAnalyzer(),IndexWriter.MaxFieldLength.LIMITED);
writer.UpdateDocument(term, doc);
writer.Optimize();
//writer.Commit();
writer.Close();
directory.Close();

需要注意,这一次,我们使用了IndexWriter对象的UpdateDocument方法,而IndexModifier没有找到现成的UpdateDocument方法。Optimize通常需要执行一下,否则索引文件中会有两个相同id的索引。

2、索引保存至内存

如果1你已经理解了,2其实可以不用细究。在IndexModifier的构造函数里有一个重载:

1
public  IndexModifier(Directory directory, Analyzer analyzer, bool  create);

下面的示例代码中第一个参数RAMDirectory就是一个Directory,我们可以把它定义成静态,创建索引的时候就完成了保存至内存的效果:

1
2
private static  RAMDirectory ramDir = null
IndexModifier  modifier = new IndexModifier(ramDir, new StandardAnalyzer(), true );

经测试,增删改查原理同1

3、利用Lucene.Net配合数据库查询

平时开发中,对于数据库中的海量数据,频繁读库可能不能满足效率和速度的需求。我们也可以利用Lucene.Net配合数据库快速查询结果。至于如何对数据库利用Lucene.Net创建索引,增删改查和同1中的介绍是一模一样的。比如本文demo中创建索引的实现,取前1000个人对他们的Id和姓名进行索引。在编码之前,我先往Person表中插入了一些数据:

1
2
3
4
5
6
7
8
INSERT  Person(FirstName,LastName,Weight,Height) VALUES ( '明' , '姚' ,200,223)
INSERT  Person(FirstName,LastName,Weight,Height) VALUES ( '建联' , '易' ,180,213)
INSERT  Person(FirstName,LastName,Weight,Height) VALUES ( '德科' , '诺维斯基' ,180,211)
INSERT  Person(FirstName,LastName,Weight,Height) VALUES ( '德怀特' , '霍华德' ,190,218)
INSERT  Person(FirstName,LastName,Weight,Height) VALUES ( '约什' , '霍华德' ,178,197)
INSERT  Person(FirstName,LastName,Weight,Height) VALUES ( '蒂姆' , '邓肯' ,183,211)
INSERT  Person(FirstName,LastName,Weight,Height) VALUES ( '凯文' , '加内特' ,182,215)
INSERT  Person(FirstName,LastName,Weight,Height) VALUES ( '德隆' , '威廉姆斯' ,166,197)

接着先取出1000个人:

1
2
string  sql = "SELECT TOP 1000 Id,FirstName,LastName FROM Person(NOLOCK)" ;
IList<Person> listPersons = EntityConvertor.QueryForList<Person>(sql, strSqlConn, null );

然后建立索引即可:

1
2
3
4
5
6
7
8
9
10
11
private void IndexDB(IndexModifier modifier,IList<Person> listModels)
    {
        SetOutput(string.Format( "正在建立数据库索引,共{0}人" ,listModels. Count ));
        foreach (Person item in  listModels)
        {
            Document doc = new Document();//创建文档,给文档添加字段,并把文档添加到索引书写器里
            doc. Add (new Field( "id" , item.Id.ToString(), Field.Store.YES, Field. Index .TOKENIZED));//存储且索引
            doc. Add (new Field( "fullname" , string.Format( "{0} {1}" ,item.FirstName,item.LastName), Field.Store.YES, Field. Index .TOKENIZED));//存储且索引
            modifier.AddDocument(doc);
        }
    }

同样的道理,最后我们也执行这两个方法(Optimize方法不是一定要做的):

1
2
modifier.Optimize();//优化索引
modifier. Close ();//关闭索引读写器

三、搜索

本文示例代码中的搜索都是利用Lucene.Net的IndexSearcher默认的比较直接简单的一个搜索方法 Search(Query query, Filter filter, int n),很多重载方法我也没有使用过:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/// <summary>
     /// 根据索引搜索
     /// </summary>
     /// <param name="keyword"></param>
     /// <returns></returns>
     private  TopDocs Search( string  keyword, string  field)
     {
         TopDocs docs = null ;
         int  n = 10; //最多返回多少个结果
         SetOutput( string .Format( "正在检索关键字:{0}" , keyword));
         try
         {
             QueryParser parser = new  QueryParser(field, new  StandardAnalyzer()); //针对内容查询
             Query query = parser.Parse(keyword); //搜索内容 contents  (用QueryParser.Parse方法实例化一个查询)
             Stopwatch watch = new  Stopwatch();
             watch.Start();
             docs = searcher.Search(query, (Filter) null , n); //获取搜索结果
             watch.Stop();
             StringBuffer sb = "索引完成,共用时:"  + watch.Elapsed.Hours + "时 "  + watch.Elapsed.Minutes + "分 "  + watch.Elapsed.Seconds + "秒 "  + watch.Elapsed.Milliseconds + "毫秒" ;
             SetOutput(sb);
         }
         catch  (Exception ex)
         {
             SetOutput(ex.Message);
             docs = null ;
         }
         return  docs;
     }
 
     /// <summary>
     /// 显示搜索结果
     /// </summary>
     /// <param name="queryResult"></param>
     private  void  ShowFileSearchResult(TopDocs queryResult)
     {
         if  (queryResult == null  || queryResult.totalHits == 0)
         {
             SetOutput( "Sorry,没有搜索到你要的结果。" );
             return ;
         }
 
         int  counter = 1;
         foreach  (ScoreDoc sd in  queryResult.scoreDocs)
         {
             try
             {
                 Document doc = searcher.Doc(sd.doc);
                 string  id = doc.Get( "id" ); //获取id
                 string  fileName = doc.Get( "filename" ); //获取文件名
                 string  contents = doc.Get( "contents" ); //获取文件内容
                 string  result = string .Format( "这是第{0}个搜索结果,Id为{1},文件名为:{2},文件内容为:{3}{4}" , counter, id, fileName, Environment.NewLine, contents);
                 SetOutput(result);
             }
             catch  (Exception ex)
             {
                 SetOutput(ex.Message);
             }
             counter++;
         }
     }

下一篇我会补充介绍一下Lucene.Net常用的搜索、排序和分页,今天偷懒一下。

最后,本文demo中的代码算不上优美,可读性还凑合,希望大家下载之后看看吧,我还在幻想万一对新手能有所帮助,或者引来某个误入的高手指点一二,于人于己那就真是善莫大焉了。







本文转自JeffWong博客园博客,原文链接:http://www.cnblogs.com/jeffwongishandsome/archive/2010/12/18/lucene-net-index.html,如需转载请自行联系原作者


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值