目录
痛点
在百度搜索引擎中,输入的关键字不完全精准,
搜索引擎能够搜索出,与关键字相近的内容,在京东搜索商品的时候,也能够搜出与之相近的内容!
然而在我们关系型数据库中,这个是无法做到的;因为数据库中是精准匹配;
在实际的业务中,更多的时候,尽管用户输入的内容不那么精准,我也希望能够搜索出与之类似的内容出来,而不是直接告诉我搜不到!
解决办法
全文索引:
*
分词:
把一个搜索的长句子,拆分成各种单词,通过单词去匹配,查询到数据;
不再是基于关系型数据库来查询了,而是基于全文索引来搜索
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索引来查询)
- Lucene.Net.Documents
- Lucene.Net.Analysis
- Lucene.Net.Index
- Lucene.Net.QueryParsers
- Lucene.Net.Search
- Lucene.Net.Store
- 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引入文件
- Lucene.Net //nuget的实现dll
- PanGu //下边的三个文件,用来实现分词
引用完盘古分词后,目录结构下会多一个Dict文件夹里边包含了盘古分词用到的字典
插入数据建立索引
/// <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);
}
}
}
- 在我使用的过程中发现一个问题,一句话,仅仅只是在Title各词中间加了一个空格,获取的数据很少。
- 想要解决问题1,手动增加空格可以解决。查出来结果总算是可以看得过去
- 但是还是推荐用ElasticSearch,Lucene感觉文档也少,在以后的应用场景也少,自己封装又太费力。