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

一、简单认识索引

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

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

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

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

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

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

1、索引保存至文件

(1)、创建索引

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

/// <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方法:

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更新一条索引

贴一下主要方法:

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的构造函数里有一个重载:

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

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

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

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

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

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个人:

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

然后建立索引即可:

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方法不是一定要做的):

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

三、搜索

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

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

demo下载:LuceneNetApp

参考:

http://www.cnblogs.com/birdshover/category/152283.html

http://lucene.apache.org/lucene.net/

http://lucene.apache.org/lucene.net/docs/

时间: 2024-08-01 00:03:04

Lucene.Net无障碍学习和使用:索引篇的相关文章

【转载】Lucene.Net无障碍学习和使用:搜索篇

在上一篇中,我们初步理解了索引的增删改查基本操作.本文着重介绍一下常用的搜索,以及搜索结果的排序和分页.本文的搜索主要是基于前一篇介绍的文本文件的索引,建议下载最后改进的demo对照着看阅读本文,同时大家可以自己动手创建一些测试文本,然后建立索引并搜索试试看. 一.初步认识搜索 先从上一篇示例代码中我们摘录一段代码看看搜索的简单实现: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 private TopDocs Searc

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

一.初步认识搜索 先从上一篇示例代码中我们摘录一段代码看看搜索的简单实现: 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 Stand

做web开发需要学习哪些技术--基础篇

做一个web网站,包含哪些技术,自己需要学习哪些技术 自己想到哪里就写到哪里 -- 给自己做的一个记录 1: 页面的展示, 一个web的开发语言  1.1 一个web的开发语言需要注意哪方面,才能符合你的网站业务发展     1:语言的选择     2:框架的设计 - 怎么符合未来业务的发展     3:对于业务的了解     4:了解http协议      那么就要了解tcp/ip协议 3: web安全     web安全又有哪些方面需要注意的呢     3.1 xss     3.2 sql

Lucene.net入门学习系列(1)

Lucene.net入门学习系列(1)-分词 Lucene.net入门学习系列(2)-创建索引 Lucene.net入门学习系列(3)-全文检索 这几天在公司实习的时候闲的蛋疼,翻了一下以往的教程和博客,看到了Lucene.net.原本想学着写一个系列的博文,由于本人水平有限,一直找不到适合的内容来写,干脆就写一个简单的Lucene.net系列文章吧.希望和大家一起学习,一起进步,有什么写错了或者有什么建议欢迎提出来. 一.引言 先说一说什么是Lucene.net.Lucene.net是Luce

Azure IoT Hub和Event Hub相关的技术系列-索引篇

Azure IoT Hub和Event Hub相关的技术系列,最近已经整理了不少了,统一做一个索引链接,置顶. Azure IoT 技术研究系列1-入门篇 Azure IoT 技术研究系列2-设备注册到Azure IoT Hub Azure IoT 技术研究系列3-设备到云.云到设备通信 Azure IoT 技术研究系列4-Azure IoT Hub的配额及缩放级别 Azure IoT 技术研究系列5-Azure IoT Hub与Event Hub比较 Azure Event Hub 技术研究系

C/C++笔试忍法帖00——开始索引篇

即将出去找个C++的实习,网上随便一搜C++笔试题,网友们整理的一套套的笔试题目,看了大有收获,原来自己还差的远呢,即便是学习过的,也不一定能回答的出来.所以看这些题目,不仅可以学到新的东西,可以起到复习基础的作用. 为此,绿整理并分类了自己已经看过一遍的C++相关的笔试题,分类成如下: 1.系统篇(涉及进程,线程等问题等) 2.网络篇(涉及各种网络协议,基础概念等) 3.数据库篇(设计数据库的概念的解释等) 4.C/C++语法特性篇(涉及堆栈,多态,内存分配,编译器特性,注意事项,技巧编程,容

jqGrid 学习笔记整理——终极篇(一)

jqGrid 学习笔记整理--终极篇(一) 本篇开始实现利用jqGrid框架实现 主从表 的显示效果及与后台交互的实例,使用的平台和上篇博文[jqGrid 学习笔记整理--进阶篇(二)](http://blog.csdn.net/dfs4df5/article/details/51108798)一致 也将使用上篇中的数据库和代码进行添加和修改,如果未看上篇的请先去看上篇,本篇不再重复贴出上篇出现的源码. 一.数据库部分 为了检索方便,这里建立了一个视图 关联两个表,设置为外键 最后如果有什么不清

jqGrid 学习笔记整理——进阶篇(二)

jqGrid 学习笔记整理--进阶篇(二 ) 本篇开始正式与后台(java语言)进行数据交互,使用的平台为 JDK:java 1.8.0_71 myEclisp 2015 Stable 2.0 Apache Tomcat-8.0.30 Mysql 5.7 Navicat for mysql 11.2.5(mysql数据库管理工具) 一.数据库部分 1.创建数据库 使用Navicat for mysql创建数据库(使用其他工具或直接使用命令行暂不介绍) 2.创建表 双击打开上步创建数据库--右击T

我们一起学习WCF 第三篇头消息验证用户身份

前言:今天我主要写的是关于头消息的一个用处验证用户信息 下面我画一个图,可以先看图 第一步:我们先开始做用户请求代码 首先:创建一个可执行的上下文对象块并定义内部传输的通道 using (OperationContextScope scope = new OperationContextScope(userClient.InnerChannel)) 然后:创建头消息 要发送的SOAP传输的内容 MessageHeader myHeaderUid = MessageHeader.CreateHea