上一集當中我們完成了Lucene基本操作中的Create與Read,這一集會將CRUD中的Update與Delete的操作方法告訴你,並且本集會著重於講解關於"Norms"與權重(Boost)在Lucene中的運作概念。
首先我們建立一個.Net 6的主控台應用程式
建立好後於右側專案右鍵選擇"管理Nuget套件",並選擇"瀏覽">於搜索列中搜尋"Lucene">安裝3.0.3最新穩定版 與 "System.Configuration.ConfigurationManager"
安裝好後就可以於專案內使用Lucene套件囉!
再來依照上一篇的教學建立一套簡單的Lucene查詢
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Documents;
using Lucene.Net.Index;
using Lucene.Net.QueryParsers;
using Lucene.Net.Search;
using Lucene.Net.Store;
var _dir = new DirectoryInfo("LuceneDocument");
if (!File.Exists(_dir.FullName))
{
System.IO.Directory.CreateDirectory(_dir.FullName);
}
var analyzer = new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_CURRENT);
CreateIndex(GetProductsInformation(), _dir, analyzer);
while (true)
{
Console.Write("請輸入欲查詢字串 :");
var searchValue = Console.ReadLine();
Search(searchValue, _dir, analyzer);
}
void CreateIndex(List<Product> information, DirectoryInfo dir, StandardAnalyzer analyzer)
{
using (var directory = FSDirectory.Open(dir))
{
using (var indexWriter = new IndexWriter(directory, analyzer, true, IndexWriter.MaxFieldLength.LIMITED))
{
foreach (var index in information)
{
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.YES, Field.Index.ANALYZED));
indexWriter.AddDocument(document);
}
indexWriter.Optimize();
indexWriter.Commit();
}
}
}
void Search(string searchValue, DirectoryInfo dir, StandardAnalyzer analyzer)
{
using (var directory = FSDirectory.Open(_dir))
{
var parser = new QueryParser(Lucene.Net.Util.Version.LUCENE_CURRENT, "Description", analyzer).Parse(searchValue);
using (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"));
}
}
}
}
List<Product> GetProductsInformation()
{
return new List<Product> {
new Product{ Id = 1, Name = "蘋果", Description = "一天一蘋果,醫生遠離我。"},
new Product{ Id = 2, Name = "橘子", Description = "醫生給娜美最珍貴的寶藏。"},
new Product{ Id = 3, Name = "梨子", Description = "我是梨子,比蘋果好吃多囉!"},
new Product{ Id = 4, Name = "葡萄", Description = "吃葡萄不吐葡萄皮,不吃葡萄倒吐葡萄皮"},
new Product{ Id = 5, Name = "榴槤", Description = "水果界的珍寶!好吃一直吃。"}
};
}
class Product
{
public long Id { get; set; }
public string Name { get; set; } = null!;
public string Description { get; set; } = null!;
}
好囉! 接下來我們要如何更新索引呢?
更新其實就是將存在的索引刪除並重新建立Document,不存在的則直接新增。
首先準備一組資料準備更新
List<Product> GetUpdateProductsInformation()
{
return new List<Product>
{
new Product{ Id = 6, Name = "香蕉", Description = "運動完後吃根香蕉補充養分。"},
new Product{ Id = 2, Name = "橘子", Description = "橘子跟柳丁你分得出來嗎?"}
};
}
*欲更新的Document必須與創建所引時使用的Document欄位相同*
void Update(string key, List<Product> information, DirectoryInfo dir, StandardAnalyzer analyzer)
{
using( var directory = FSDirectory.Open(dir))
{
using(var indexWriter = new IndexWriter(directory, analyzer, false, IndexWriter.MaxFieldLength.LIMITED))
{
foreach (var index in information)
{
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.YES, Field.Index.ANALYZED));
indexWriter.UpdateDocument(new Term("Name", key) ,document);
}
}
}
}
來測試看看
可以看見 Name = 橘子 的索引已經改為我們新準備的資料囉。
再來是刪除!
與更新非常相似,只需要使用deleteDocument()就可以了。
void Delete(string key, DirectoryInfo dir, StandardAnalyzer analyzer)
{
using (var directory = FSDirectory.Open(dir))
{
using (var indexWriter = new IndexWriter(directory, analyzer, false, IndexWriter.MaxFieldLength.LIMITED))
{
indexWriter.DeleteDocuments(new Term("Name", key));
indexWriter.Optimize();
indexWriter.Commit();
}
}
}
再來看看輸出結果
可以發現 Score :0.7554128, Id :2, Name :橘子, Description :醫生給娜美最珍貴的寶藏。這筆索引已經被移除囉!
可以發現筆者於更新或刪除時都是輸入單一字來做異動,除了表達可以對索引做複合更動外,
是因為更新與刪除索引同樣會使用到分詞器(analyzer),
*所輸入的索引值非ID等數值時必須要配合分詞器的分詞能力*才能取得所想異動的索引喔!
Boost是什麼呢?
Boost 分為 :
1. Index Time Boost : 在建立索引時就計算好的值。例如上一篇中提到的(NORMS)
2. Query Time Boost : 查詢時賦與搜尋條件不同的值以影響結果。
我們先來測試Index Time Boost的部分
void CreateIndexWithBoost(List<Product> information, DirectoryInfo dir, StandardAnalyzer analyzer)
{
using (var directory = FSDirectory.Open(dir))
{
using (var indexWriter = new IndexWriter(directory, analyzer, true, IndexWriter.MaxFieldLength.LIMITED))
{
foreach (var index in information)
{
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.YES, Field.Index.ANALYZED));
document.GetField("Name").Boost = 1.5F;
document.GetField("Description").Boost = 0.5F;
indexWriter.AddDocument(document);
}
indexWriter.Optimize();
indexWriter.Commit();
}
}
}
並記得重新CreateIndex才能刷新欄位的權重值喔。
很明顯的搜尋出來的Score分數變動了! 但是有沒有發現明明Name欄位的Boost改成了1.5,蘋果的數值卻仍然只有一半呢?
這是因為我們的Search中所參照的欄位為Description,所以在計算Score的時候其實是完全沒有參與的喔!
另外要記得,使用Index Time Boost的時候,欲給予銓重分配的欄位Field.Index不能使用NO_NORMS,不然這個欄位並不會紀錄權重的資料。
再來我們試試看Query Time Boost
void SearchWithBoost(string searchValue, DirectoryInfo dir, StandardAnalyzer analyzer)
{
using (var directory = FSDirectory.Open(_dir))
{
using (var indexSearcher = new IndexSearcher(directory))
{
var query = new QueryParser(Lucene.Net.Util.Version.LUCENE_CURRENT, "Name", analyzer).Parse(searchValue);
var query2 = new QueryParser(Lucene.Net.Util.Version.LUCENE_CURRENT, "Description", analyzer).Parse(searchValue);
query.Boost = 2.0F;
query2.Boost = 0.5F;
BooleanQuery booleanQuery = new BooleanQuery();
booleanQuery.Add(query, Occur.SHOULD);
booleanQuery.Add(query2, Occur.SHOULD);
var hits = indexSearcher.Search(booleanQuery, 20);
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"));
}
}
}
}
這次我們搜尋兩個欄位"Name"與"Description",並使用 BooleanQuery來將其組合。
BooleanQuery中的 Occur有三種參數 : "MUST","MUST_NOT","SHOULD",功能與字面上的意思一樣為"必須要有","必須沒有"與"有無都包含"。
查詢出來的分數就不一樣囉!
以上就是這一次的分享,Lucene是一款容易入門但是要實際上戰場卻又十分複雜的功能,想要達成真正高效能的全文檢索,在前期的文件規畫配置與資料的權重配比都是一個巨大的挑戰。未來會繼續分享關於Lucene的其他有趣功能,還請繼續期待呦!
另外也可以到GitHub下載我的範例來參考呦!
GitHub: https://github.com/g13579112000/Lucene
參考文件:
1. 黑暗大大的全文檢索筆記 : https://blog.darkthread.net/blog/lucene-net-notes-1/
2. Makble : http://makble.com/lucene-field-boost-example
3. CSDN Jack2013tong 文章 : https://blog.csdn.net/huwei2003/article/details/53408388
梨子, 2022/4/20 下午 09:34:03
寫一下, 以備不時之需, Codepage 和 Name 可以用來取得 encoding.
CodePage, DisplayName, Name
37, IBM EBCDIC (美國-加拿大), IBM037
437, OEM 美國, IBM437
500, IBM EBCDIC (國際), IBM500
708, 阿拉伯文 (ASMO 708), ASMO-708
720, 阿拉伯文 (DOS), DOS-720
737, 希臘文 (DOS), ibm737
775, 波羅的海文 (DOS), ibm775
850, 西歐語系 (DOS), ibm850
852, 中歐語系 (DOS), ibm852
855, OEM 斯拉夫文, IBM855
857, 土耳其文 (DOS), ibm857
858, OEM 多語系拉丁文 I, IBM00858
860, 葡萄牙文 (DOS), IBM860
861, 冰島文 (DOS), ibm861
862, 希伯來文 (DOS), DOS-862
863, 加拿大法文 (DOS), IBM863
864, 阿拉伯文 (864), IBM864
865, 北歐字母 (DOS), IBM865
866, 斯拉夫文 (DOS), cp866
869, 希臘文,現代 (DOS), ibm869
870, IBM EBCDIC (多語系拉丁文 2), IBM870
874, 泰文 (Windows), windows-874
875, IBM EBCDIC (希臘現代), cp875
932, 日文 (Shift-JIS), shift_jis
936, 簡體中文 (GB2312), gb2312
949, 韓文, ks_c_5601-1987
950, 繁體中文 (Big5), big5
1026, IBM EBCDIC (土耳其拉丁文 5), IBM1026
1047, IBM 拉丁文 1, IBM01047
1140, IBM EBCDIC (美國-加拿大-歐洲), IBM01140
1141, IBM EBCDIC (德國-歐洲), IBM01141
1142, IBM EBCDIC (丹麥-挪威-歐洲), IBM01142
1143, IBM EBCDIC (芬蘭-瑞典-歐洲), IBM01143
1144, IBM EBCDIC (義大利-歐洲), IBM01144
1145, IBM EBCDIC (西班牙-歐洲), IBM01145
1146, IBM EBCDIC (英國-歐洲), IBM01146
1147, IBM EBCDIC (法國-歐洲), IBM01147
1148, IBM EBCDIC (國際-歐洲), IBM01148
1149, IBM EBCDIC (冰島-歐洲), IBM01149
1200, Unicode, utf-16
1201, Unicode (位元組由大到小), utf-16BE
1250, 中歐語系 (Windows), windows-1250
1251, 斯拉夫文 (Windows), windows-1251
1252, 西歐語系 (Windows), Windows-1252
1253, 希臘文 (Windows), windows-1253
1254, 土耳其文 (Windows), windows-1254
1255, 希伯來文 (Windows), windows-1255
1256, 阿拉伯文 (Windows), windows-1256
1257, 波羅的海文 (Windows), windows-1257
1258, 越南文 (Windows), windows-1258
1361, 韓文 (Johab), Johab
10000, 西歐語系 (Mac), macintosh
10001, 日文 (Mac), x-mac-japanese
10002, 繁體中文 (Mac), x-mac-chinesetrad
10003, 韓文 (Mac), x-mac-korean
10004, 阿拉伯文 (Mac), x-mac-arabic
10005, 希伯來文 (Mac), x-mac-hebrew
10006, 希臘文 (Mac), x-mac-greek
10007, 斯拉夫文 (Mac), x-mac-cyrillic
10008, 簡體中文 (Mac), x-mac-chinesesimp
10010, 羅馬尼亞文 (Mac), x-mac-romanian
10017, 烏克蘭文 (Mac), x-mac-ukrainian
10021, 泰文 (Mac), x-mac-thai
10029, 中歐語系 (Mac), x-mac-ce
10079, 冰島文 (Mac), x-mac-icelandic
10081, 土耳其文 (Mac), x-mac-turkish
10082, 克羅埃西亞文 (Mac), x-mac-croatian
12000, Unicode (UTF-32), utf-32
12001, Unicode (UTF-32 位元組由大到小), utf-32BE
20000, 繁體中文 (CNS), x-Chinese-CNS
20001, TCA 台灣, x-cp20001
20002, 繁體中文 (Eten), x-Chinese-Eten
20003, IBM5550 台灣, x-cp20003
20004, TeleText 台灣, x-cp20004
20005, Wang 台灣, x-cp20005
20105, 西歐語系 (IA5), x-IA5
20106, 德文 (IA5), x-IA5-German
20107, 瑞典文 (IA5), x-IA5-Swedish
20108, 挪威文 (IA5), x-IA5-Norwegian
20127, US-ASCII, us-ascii
20261, T.61, x-cp20261
20269, ISO-6937, x-cp20269
20273, IBM EBCDIC (德國), IBM273
20277, IBM EBCDIC (丹麥-挪威), IBM277
20278, IBM EBCDIC (芬蘭-瑞典), IBM278
20280, IBM EBCDIC (義大利), IBM280
20284, IBM EBCDIC (西班牙), IBM284
20285, IBM EBCDIC (UK), IBM285
20290, IBM EBCDIC (日文片假名), IBM290
20297, IBM EBCDIC (法國), IBM297
20420, IBM EBCDIC (阿拉伯文), IBM420
20423, IBM EBCDIC (希臘文), IBM423
20424, IBM EBCDIC (希伯來文), IBM424
20833, IBM EBCDIC (韓文擴充), x-EBCDIC-KoreanExtended
20838, IBM EBCDIC (泰國), IBM-Thai
20866, 斯拉夫文 (KOI8-R), koi8-r
20871, IBM EBCDIC (冰島), IBM871
20880, IBM EBCDIC (斯拉夫俄文), IBM880
20905, IBM EBCDIC (土耳其), IBM905
20924, IBM 拉丁文 1, IBM00924
20932, 日文 (JIS 0208-1990 和 0212-1990), EUC-JP
20936, 簡體中文 (GB2312-80), x-cp20936
20949, 韓文 Wansung, x-cp20949
21025, IBM EBCDIC (斯拉夫塞爾維亞文-保加利亞文), cp1025
21866, 斯拉夫文 (KOI8-U), koi8-u
28591, 西歐語系 (ISO), iso-8859-1
28592, 中歐語系 (ISO), iso-8859-2
28593, 拉丁文 3 (ISO), iso-8859-3
28594, 波羅的海文 (ISO), iso-8859-4
28595, 斯拉夫文 (ISO), iso-8859-5
28596, 阿拉伯文 (ISO), iso-8859-6
28597, 希臘文 (ISO), iso-8859-7
28598, 希伯來文 (ISO-Visual), iso-8859-8
28599, 土耳其文 (ISO), iso-8859-9
28603, 愛沙尼亞文 (ISO), iso-8859-13
28605, 拉丁文 9 (ISO), iso-8859-15
29001, 歐洲, x-Europa
38598, 希伯來文 (ISO-Logical), iso-8859-8-i
50220, 日文 (JIS), iso-2022-jp
50221, 日文 (JIS-Allow 1 byte Kana), csISO2022JP
50222, 日文 (JIS-Allow 1 byte Kana - SO/SI), iso-2022-jp
50225, 韓文 (ISO), iso-2022-kr
50227, 簡體中文 (ISO-2022), x-cp50227
51932, 日文 (EUC), euc-jp
51936, 簡體中文 (EUC), EUC-CN
51949, 韓文 (EUC), euc-kr
52936, 簡體中文 (HZ), hz-gb-2312
54936, 簡體中文 (GB18030), GB18030
57002, ISCII 梵文語系, x-iscii-de
57003, ISCII 孟加拉文, x-iscii-be
57004, ISCII 坦米爾文, x-iscii-ta
57005, ISCII 特拉古文, x-iscii-te
57006, ISCII 阿薩姆文, x-iscii-as
57007, ISCII 歐利亞文, x-iscii-or
57008, ISCII 坎那達文, x-iscii-ka
57009, ISCII 馬來亞拉姆文, x-iscii-ma
57010, ISCII 古吉拉特文, x-iscii-gu
57011, ISCII 旁遮普語, x-iscii-pa
65000, Unicode (UTF-7), utf-7
65001, Unicode (UTF-8), utf-8
Bike, 2016/11/12 上午 10:12:03