22:lucene.net全文检索

目录

痛点

在百度搜索引擎中,输入的关键字不完全精准,
搜索引擎能够搜索出,与关键字相近的内容,在京东搜索商品的时候,也能够搜出与之相近的内容!
然而在我们关系型数据库中,这个是无法做到的;因为数据库中是精准匹配;
在实际的业务中,更多的时候,尽管用户输入的内容不那么精准,我也希望能够搜索出与之类似的内容出来,而不是直接告诉我搜不到!

解决办法

全文索引:
*
分词:
把一个搜索的长句子,拆分成各种单词,通过单词去匹配,查询到数据;
不再是基于关系型数据库来查询了,而是基于全文索引来搜索
1.*
2.人民
3.华人
4.中华

lucene.net是什么

Lucene.net是一个高性能的全能的全文检索的搜索引擎框架类库,完全使用C#开发。它是一种技术,任何一种需要全文检索的应用。
Lucene本来是在java中,在后来,才出现在C#中;
Version:3.0.3----(现在好像已经不更新了,最新的测试版bug比较多)
http://lucenenet.apache.org/download/download.html


如果需要搭建一个全文检索,或者日志系统。建议使用ElasticSearch+Logstash+Kibana配合NEST来使用,官方有操作文档。可以查看学习。
ps:ElasticSearch的9200商品不要向公网开放。

lucene.net七大对象介绍和多种query方式

一进一出;
一进:从关系型数据库中生成Lucene索引(要通过分词存储);
一出:查询,通过查询条件,也经过分词—到Lucene索引中去匹配得到数据(不再是基于关系型数据库来查询,而是基于Lucene索引来查询)
22:lucene.net全文检索

  1. Lucene.Net.Documents
  2. Lucene.Net.Analysis
  3. Lucene.Net.Index
  4. Lucene.Net.QueryParsers
  5. Lucene.Net.Search
  6. Lucene.Net.Store
  7. Lucene.Net.Util

lucene存储结构

Documents

提供一个简单的Document类,一个document只不过包括一系列的命名了(named)的Fields(域),它们的内容可以是文本(strings)也可以是一个io.Reader的实例。
用来存储数据库中某一条记录—数据库中一条记录就对应一个document;在存储的时候,肯定不止一个document;

Analysis

定义了一个抽象的Analyser API,用于将text文本从一个Reader转换成一个TokenStream,即包括一些Tokens的枚举容器(enumeration)。一个TokenStream的组成(compose)是通过在一个Tokenizer的输出的结果上再应用TokenFilters生成的。一些少量的Analysers实现已经提供,包括StopAnalyzer和基于语法(gramar-based)分析的StandardAnalyzer
分词器:把句子进行分词;

Index

提供两个主要类,一个是IndexWriter用于创建索引并添加文档(document),另一个是IndexSearcher用于访问索引中的数据。
完成索引的读写;

QueryParsers

实现一个QueryParser。
查询的时候,需要组建的各种查询条件;
给你一个长句子,通过分词,拆分生成各种查询条件;

Search

提供数据结构(data structures)来呈现(represent)查询(queries):TermQuery用于单个的词(individual words),PhraseQuery用于短语,BooleanQuery用于通过boolean关系组合(combinations)在一起的queries。而抽象的Searcher用于转变queries为命中的结果(hits)。IndexSearcher实现了在一个单独(single)的IndexReader上检索
从索引中,根据你提供的条件提取数据;

Store

定义了一个抽象的类用于存储呈现的数据(storing persistent data),即Directory(目录),一个收集器(collection)包含了一些命名了的文件(named files),它们通过一个IndexOutput来写入,以及一个IndexInput来读取。提供了两个实现,FSDirectory使用一个文件系统目录来存储文件,而另一个RAMDirectory则实现了将文件当作驻留内存的数据结构(memory-resident data structures)
用来保存索引数据,包括文件夹,相关文件;

Search

Search TermQuery

TermQuery:单元查询 new Term(“title”,“张三”)
title:张三

Search BoolenQuery

BoolenQuery:
new Term(“title”,“张三”) and new Term(“title”,“李四”)
title:张三 + title:李四 and ==+

new Term("title","张三")   or new Term("title","李四")    	
title:张三  title:李四      or== 空格

Search WildcardQuery

WildcardQuery:通配符
new Term(“title”,“张?”) title:张? 匹配以“张”字开头

Search PrefixQuery

PrefixQuery:前缀查询 以xx开头
title:张*

Search PhraseQuery

PhraseQuery:间隔距离 包含没有 包含中华 而且二者距离不能超过5
title: “中华 *"~5
中华 *

Search FuzzyQuery

FuzzyQuery:近似查询,ibhone----iphone title:ibhone~

Search RangeQuery

RangeQuery:范围查询 [1,100] {1,100}
开区间,闭区间
价格 时间

lucene使用流程

Nuget引入文件

  1. Lucene.Net //nuget的实现dll
  2. PanGu //下边的三个文件,用来实现分词
    引用完盘古分词后,目录结构下会多一个Dict文件夹里边包含了盘古分词用到的字典

22:lucene.net全文检索

插入数据建立索引

        /// <summary>
        /// 初始化索引
        /// </summary>
        public static void InitIndex()
        {
            List<Commodity> commodityList = GetList();//获取数据源
            FSDirectory directory = FSDirectory.Open(StaticConstant.TestIndexPath);//文件夹(StaticConstant.TestIndexPath)本地存储路径
            //经过分词以后把内容写入到硬盘
            //PanGuAnalyzer 盘古分词;*,从后往前匹配,匹配到和词典一样的词,就保存起来;建议大家去看看盘古分词的官网;词典是可以我们手动去维护;
            //城会玩---网络流行词--默认没有,盘古分词,可以由我们自己把这些词给添加进去;
            using (IndexWriter writer = new IndexWriter(directory, new PanGuAnalyzer(), true, IndexWriter.MaxFieldLength.LIMITED))//索引写入器
            {
                foreach (Commodity commdity in commodityList)
                {
                    int k = 22;
                    //for (int k = 0; k < 10; k++)
                    //{
                        Document doc = new Document();//一条数据
                        doc.Add(new Field("id", commdity.Id.ToString(), Field.Store.NO, Field.Index.NOT_ANALYZED));//一个字段  列名  值   是否保存值  是否分词
                        doc.Add(new Field("title", commdity.Title, Field.Store.YES, Field.Index.ANALYZED));
                        doc.Add(new Field("url", commdity.Url, Field.Store.NO, Field.Index.NOT_ANALYZED));
                        doc.Add(new Field("imageurl", commdity.ImageUrl, Field.Store.NO, Field.Index.NOT_ANALYZED));
                        doc.Add(new Field("content", "this is lucene working,powerful tool " + k, Field.Store.YES, Field.Index.ANALYZED));
                        doc.Add(new NumericField("price", Field.Store.YES, true).SetDoubleValue((double)(commdity.Price + k)));
                        //doc.Add(new NumericField("time", Field.Store.YES, true).SetLongValue(DateTime.Now.ToFileTimeUtc()));
                        doc.Add(new NumericField("time", Field.Store.YES, true).SetIntValue(int.Parse(DateTime.Now.ToString("yyyyMMdd")) + k));
                        writer.AddDocument(doc);//写进去
                    //}
                }
                writer.Optimize();//优化  就是合并
            }
        }

查询数据

public static void Show()
        {
            FSDirectory dir = FSDirectory.Open(StaticConstant.TestIndexPath);
            IndexSearcher searcher = new IndexSearcher(dir);//查找器
            {


                FuzzyQuery query = new FuzzyQuery(new Term("title", "咖啡产品同时高中政治"));
                //TermQuery query = new TermQuery(new Term("title", "周年"));//包含
                TopDocs docs = searcher.Search(query, null, 10000);//找到的数据
                foreach (ScoreDoc sd in docs.ScoreDocs)
                {
                    Document doc = searcher.Doc(sd.Doc);
                    Console.WriteLine("***************************************");
                    Console.WriteLine(string.Format("id={0}", doc.Get("id")));
                    Console.WriteLine(string.Format("title={0}", doc.Get("title")));
                    Console.WriteLine(string.Format("time={0}", doc.Get("time")));
                    Console.WriteLine(string.Format("price={0}", doc.Get("price")));
                    Console.WriteLine(string.Format("content={0}", doc.Get("content")));
                }
                Console.WriteLine("1一共命中了{0}个", docs.TotalHits);
            }

            QueryParser parser = new QueryParser(Version.LUCENE_30, "title", new PanGuAnalyzer());//解析器
            {
                string keyword = "高中政治人教新课标选修生活中的法律常识";
                //string keyword = "高中政治 人 教 新课 标 选修 生活 中的 法律 常识 咖啡 产品 同时 高中 政治";
                {
                    Query query = parser.Parse(keyword);
                    TopDocs docs = searcher.Search(query, null, 10000);//找到的数据

                    int i = 0;
                    foreach (ScoreDoc sd in docs.ScoreDocs)
                    {
                        if (i++ < 1000)
                        {
                            Document doc = searcher.Doc(sd.Doc);
                            Console.WriteLine("***************************************");
                            Console.WriteLine(string.Format("id={0}", doc.Get("id")));
                            Console.WriteLine(string.Format("title={0}", doc.Get("title")));
                            Console.WriteLine(string.Format("time={0}", doc.Get("time")));
                            Console.WriteLine(string.Format("price={0}", doc.Get("price")));
                        }
                    }
                    Console.WriteLine($"一共命中{docs.TotalHits}");
                }
                {
                    Query query = parser.Parse(keyword);
                    NumericRangeFilter<int> timeFilter = NumericRangeFilter.NewIntRange("time", 20090101, 20201231, true, true);//过滤
                    SortField sortPrice = new SortField("price", SortField.DOUBLE, false);//false::降序
                    SortField sortTime = new SortField("time", SortField.INT, true);//true:升序
                    Sort sort = new Sort(sortTime, sortPrice);//排序 哪个前哪个后

                    TopDocs docs = searcher.Search(query, timeFilter, 10000, sort);//找到的数据

                    //可以做什么?就可以分页查询!
                    int i = 0;
                    foreach (ScoreDoc sd in docs.ScoreDocs)
                    {
                        if (i++ < 1000)
                        {
                            Document doc = searcher.Doc(sd.Doc);
                            Console.WriteLine("***************************************");
                            Console.WriteLine(string.Format("id={0}", doc.Get("id")));
                            Console.WriteLine(string.Format("title={0}", doc.Get("title")));
                            Console.WriteLine(string.Format("time={0}", doc.Get("time")));
                            Console.WriteLine(string.Format("price={0}", doc.Get("price")));
                        }
                    }
                    Console.WriteLine("3一共命中了{0}个", docs.TotalHits);
                }
            }
        }
  1. 在我使用的过程中发现一个问题,一句话,仅仅只是在Title各词中间加了一个空格,获取的数据很少。
  2. 想要解决问题1,手动增加空格可以解决。查出来结果总算是可以看得过去
  3. 但是还是推荐用ElasticSearch,Lucene感觉文档也少,在以后的应用场景也少,自己封装又太费力。
上一篇:ElasticSearch的安装和开启


下一篇:2020最全的Lucene7 入门教程