Lucene.Net是一套C#开源全文索引库,其主要包含了:
· Index : 提供索引的管理与词组的排序
· Search : 提供查询相关功能
· Store : 支援资料储存管理,包括I/O操作
· Util : 共用套件
· Documents : 负责描述索引储存时的文件结构管理
· QueryParsers : 提供查询语法
· Analysis : 负责分析内容
要达到高效能的全文检索让机器可以明白我们的语言,最重要的关键就是"分词器"了。
试想一下这一句话你会如何拆分成一段一段的关键字呢?
"一天一苹果,医生远离我"
还有英文版本
"An apple a day, doctor keep me away."
中文版本的拆分:
"一天"、"一"、"苹果"、"医生"、"远离"、"我"
英文版本的拆分:
"apple"、"day"、"doctor"、"keep"、"me"、"away"
有没有注意到不同语系所分析出来的关键字有一点不一样呢?
而在Lucene中分词的工作会交给Analysis来完成,
不过我们可以依照不同的语系去选择想使用的分词器(Analyzer)!
首先简单说明一下Lucene的实作流程
确认主要搜寻的语系来决定使用的分词器(analyzer)
建立Document依照analyzer汇入资料
(前置完成)
建立IndexSearcher导入准备好的Document
建立Parser来分析SearchValue
使用IndexSearcher分析Parser取得结果(Hits)
本专案使用的是Lucene.Net 3.0.3
接下来我们来建立一个提供查询使用的Document。
// 取得或建立Lucene文件资料夹
if (!File.Exists(_dir.FullName))
{
System.IO.Directory.CreateDirectory(_dir.FullName);
}
// Asp.Net Core需要於Nuget安装System.Configuration.ConfigurationManager提供用户端应用程序的组态档存取
Lucene.Net.Store.Directory directory = FSDirectory.Open(_dir);
// 选择分词器
var analyzer = new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_CURRENT);
// 资料来源
var repository = new Repository();
// 依照指定的文件结构来建立
var indexWriter = new IndexWriter(directory, analyzer, true, IndexWriter.MaxFieldLength.LIMITED);
foreach (var index in repository)
{
var document = new Document();
document.Add(new Field("Id", index.Id.ToString(), Field.Store.YES, Field.Index.NO));
document.Add(new Field("Name", index.Name, Field.Store.YES, Field.Index.ANALYZED));
document.Add(new Field("Description", index.Description, Field.Store.NO, Field.Index.ANALYZED));
indexWriter.AddDocument(document);
}
indexWriter.Optimize();
indexWriter.Commit();
indexWriter.Dispose();
如此一来我们就建立好Lucene的基本配备罗!
其中analyzer的部分我们使用Lucene.Net预设,
要特别注意的是,其处理中文语系的能力非常之烂!
之後再写一篇文章深入探讨。
再来值得一提的是
document.Add(new Field("Id", index.Id.ToString(), Field.Store.YES, Field.Index.NO));
前两个参数就是Key跟Value,可以简单理解为栏位与其内容。
後面两个参数是重点!
Store: 代表是否储存这个Key的Value
例如在google打上台南美食会搜索出许多不同的文章连结,
不过google给你的资料中最重要的不是文章内容(Description),
而是哪一篇文章(Name)与台南美食最有关系。
假如今天我只要回传一个列表而不用提示文章中有哪些内容,
那麽我就可以选择给"Description" Field.Store.No来节省空间。
Index:
· NO - 不加入索引,这个内容只需要随着结果出炉,不需要在查询的时候被考虑。
· ANALYZED、NOT_ANALYZED - 是否使用分词
· NO_NORMS - 关闭权重功能
或许许多人会对权重功能(NORMS)感到疑惑,
简单的举个例子
{ Id=1, Key="苹果", Value="一天一苹果,医生远离我。"}
{ Id=2, Key="橘子", Value="医生给娜美最珍贵的宝藏。"}
{ Id=3, Key="梨子", Value="我是梨子,比苹果苹果好吃多罗!"}
当我搜寻"苹果"的时候结果会是
{ Id=1, MatchKey=1, MatchValue=1, Score=(15) + (12) = 7}
{ Id=3, MatchKey=0, MatchValue=1, Score=(05) + (22) = 4}
有发现了吗?
虽然同样都对中两个结果但是Id 1的资料Key值中有包含关键字,
因此得到较高的分数排在Id 3前方
准备好Document了,我们可以开始来实际使用看看罗!
// 决定所要搜索的栏位
var parser = new QueryParser(Lucene.Net.Util.Version.LUCENE_CURRENT, "Description", analyzer).Parse(searchValue);
// 提供刚刚建立的Document
var indexSearcher = new IndexSearcher(directory);
// 搜寻取出结果的数量
var queryLimit = 20;
// 开始搜寻!
var hits = indexSearcher.Search(parser, queryLimit);
if (!hits.ScoreDocs.Any())
{
Console.WriteLine("查无相关结果。");
return;
}
Document doc;
foreach (var hit in hits.ScoreDocs)
{
doc = indexSearcher.Doc(hit.Doc);
Console.WriteLine("Score :" + hit.Score + ", Id :" + doc.Get("Id") + ", Name :" + doc.Get("Name") + ", Description :" + doc.Get("Description"));
}
最後的结果(Hits),是需要再回到Document去捞出对应的资料喔!
是不是非常简单呢?
笔者写了一个简单的范例在GitHub上,秉持着追求新技术的心使用了.Net 6,还请各位大大多多包涵。
有中英文两种Repository,只需要在上方的DI注入切换就可以罗!
GitHub连结: https://github.com/g13579112000/Lucene
笔者第一次撰写这种教学文章,有哪边错误的非常欢迎一起来讨论指教。
之後有机会再撰写Lucene更深入的应用方面,
例如权重的分配与分词器的选择与使用。
感谢您的阅读。
参考文献:
1.黑暗大大的全文检索笔记: https://blog.darkthread.net/blog/lucene-net-notes-1/
2.使用.Net实现全文检索: https://blog.csdn.net/huwei2003/article/details/53408388
3.伊凡的部落格: http://irfen.me/5-lucene4-9-learning-record-lucene-analysis-tokenizer/
4.纯净天空代码范例: https://vimsky.com/zh-tw/examples/detail/csharp-ex-Lucene.Net.Documents-Document---class.html
>>: Laravel 技术笔记 (一)【Routing 路由】
昨天我们把试算表的前置作业完成,今天我们来看看将别人填表单後送到试算表中的内容怎麽被读取。 首先先来...
JS 原生写法处理 AJAX XMLHttpRequest 透过建立一个XMLHttpRequest...
我以前最常用的接案网叫做爱苏活,当年不像现在有这麽多免费的接案社团,大部分都是要付年费才能取得发案者...
跨站点脚本(Cross-Site Scripting:XSS)和注入(Injection) 输入验证...
好的,连假最後一天,我们来个小篇章,就是Span啦,Span可以做到的事情有很多,如 *更改特定位...