C# Lucene.Net
最后修改于 2023 年 7 月 5 日
在本文中,我们将展示如何使用 Lucene.Net 库。这是一个介绍性材料,涵盖 Lucene.Net 的基础知识。
Lucene.Net
Lucene.Net 是 Java Lucene 搜索库的 .NET 端口。 Lucene 提供全文索引和搜索功能。
要使用 Lucene,我们首先创建一个索引。 索引是一个数据结构,它将内容映射到其位置。 索引可以存储在文件系统中或内存中。 创建文档索引后,我们构建一个查询并在索引上执行搜索。 最后,返回与查询匹配的结果。
Lucene 基本定义
术语 表示文本中的一个单词,而 短语 是一组单词。 查询 是用于匹配文档中文本的语法。 有多种查询类型,包括 RegexpQuery、TermQuery、WildcardQuery 和 PrefixQuery。
分析器 构建令牌流来分析文本。 因此,它代表从文本中提取索引术语的策略。 IndexSearcher 提供用于搜索索引的工具。
文档 是字段的集合。 每个字段都有一个与之关联的值。 字段存储我们要索引和搜索的术语。
搜索结果是最佳匹配文档的集合。 结果包含在 TopDocs 类中。
$ dotnet add package Lucene.Net --prerelease $ dotnet add package Lucene.Net.Analysis.Common --prerelease
这是需要添加到我们项目中的两个基本包。
Lucene.Net 简单示例
以下是一个简单的 Lucene.Net 示例。
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Documents;
using Lucene.Net.Index;
using Lucene.Net.Search;
using Lucene.Net.Store;
using Lucene.Net.Util;
const LuceneVersion ver = LuceneVersion.LUCENE_48;
using var ramDir = new RAMDirectory();
using var analyzer = new StandardAnalyzer(ver);
var idxCfg = new IndexWriterConfig(ver, analyzer);
idxCfg.OpenMode = OpenMode.CREATE;
using var writer = new IndexWriter(ramDir, idxCfg);
var doc = new Document();
doc.Add(new TextField("phrase", "an old dog", Field.Store.YES));
writer.AddDocument(doc);
doc = new Document();
doc.Add(new TextField("phrase", "an old falcon in the sky", Field.Store.YES));
writer.AddDocument(doc);
doc = new Document();
doc.Add(new TextField("phrase", "a stormy night", Field.Store.YES));
writer.AddDocument(doc);
writer.Commit();
using DirectoryReader reader = writer.GetReader(applyAllDeletes: true);
var searcher = new IndexSearcher(reader);
var query = new TermQuery(new Term("phrase", "old"));
TopDocs topDocs = searcher.Search(query, n: 3);
int hits = topDocs.TotalHits;
Console.WriteLine($"Matching results: {hits}");
foreach (var sdoc in topDocs.ScoreDocs)
{
Document mdoc = searcher.Doc(sdoc.Doc);
Console.WriteLine(mdoc.Get("phrase"));
}
在此示例中,我们定义一个包含三个文档的简单索引。 我们对索引执行基本查询。
using var ramDir = new RAMDirectory();
索引存储在 RAM 中。
using var analyzer = new StandardAnalyzer(ver);
我们创建一个新的 StandardAnalyzer 来处理文本。 它使用默认的停止词构建一个分析器
var idxCfg = new IndexWriterConfig(ver, analyzer); idxCfg.OpenMode = OpenMode.CREATE; using var writer = new IndexWriter(ramDir, idxCfg);
创建一个 IndexWriter。 它创建并维护一个索引。
var doc = new Document();
doc.Add(new TextField("phrase", "an old dog", Field.Store.YES));
writer.AddDocument(doc);
doc = new Document();
doc.Add(new TextField("phrase", "an old falcon in the sky", Field.Store.YES));
writer.AddDocument(doc);
doc = new Document();
doc.Add(new TextField("phrase", "a stormy night", Field.Store.YES));
writer.AddDocument(doc);
我们创建三个文档。 每个文档都有一个字段。
writer.Commit();
我们将索引数据刷新并提交到目录。
using DirectoryReader reader = writer.GetReader(applyAllDeletes: true); var searcher = new IndexSearcher(reader);
创建 DirectoryReader 和 IndexSearcher。 它们用于执行搜索。
var query = new TermQuery(new Term("phrase", "old"));
我们定义一个基本的 TermQuery。 它包含一个术语。
TopDocs topDocs = searcher.Search(query, n: 3);
我们使用给定的查询进行搜索。 我们寻找任何前三个结果。 搜索返回 TopDocs,它是搜索者返回的匹配项。
int hits = topDocs.TotalHits;
Console.WriteLine($"Matching results: {hits}");
我们使用 TotalHits 属性获取命中数。
foreach (var sdoc in topDocs.ScoreDocs)
{
Document mdoc = searcher.Doc(sdoc.Doc);
Console.WriteLine(mdoc.Get("phrase"));
}
我们遍历结果并打印匹配的短语。
$ dotnet run Matching results: 2 an old dog an old falcon in the sky
Lucene.Net MultiPhraseQuery
MultiPhraseQuery 允许我们添加多个查询。
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Documents;
using Lucene.Net.Index;
using Lucene.Net.Search;
using Lucene.Net.Store;
using Lucene.Net.Util;
const LuceneVersion ver = LuceneVersion.LUCENE_48;
using var ramDir = new RAMDirectory();
using var analyzer = new StandardAnalyzer(ver);
var idxCfg = new IndexWriterConfig(ver, analyzer);
idxCfg.OpenMode = OpenMode.CREATE;
using var writer = new IndexWriter(ramDir, idxCfg);
createIndex(writer);
doSearch(writer);
void createIndex(IndexWriter writer)
{
var doc = new Document();
doc.Add(new TextField("phrase", "an old dog", Field.Store.YES));
writer.AddDocument(doc);
doc = new Document();
doc.Add(new TextField("phrase", "an old falcon in the sky", Field.Store.YES));
writer.AddDocument(doc);
doc = new Document();
doc.Add(new TextField("phrase", "a stormy night", Field.Store.YES));
writer.AddDocument(doc);
writer.Commit();
}
void doSearch(IndexWriter writer)
{
using DirectoryReader reader = writer.GetReader(applyAllDeletes: true);
var searcher = new IndexSearcher(reader);
var mquery = new MultiPhraseQuery
{
new Term("phrase", "old"),
new Term("phrase", "falcon")
};
TopDocs topDocs = searcher.Search(mquery, n: 3);
int hits = topDocs.TotalHits;
Console.WriteLine($"Matching results: {hits}");
foreach (var sdoc in topDocs.ScoreDocs)
{
Document mdoc = searcher.Doc(sdoc.Doc);
Console.WriteLine(mdoc.Get("phrase"));
}
}
在此示例中,我们构建一个 MultiPhraseQuery。
var mquery = new MultiPhraseQuery
{
new Term("phrase", "old"),
new Term("phrase", "falcon")
};
MultiPhraseQuery 由两个术语组成。 搜索结果应包含这两个术语。
$ dotnet run Matching results: 1 an old falcon in the sky
Lucene.Net QueryParser
QueryParser 用于从自定义查询语法 Lucene 语法定义查询。
title:"bacon" AND body:"forest"
此查询在 title 字段中搜索 bacon,在 body 字段中搜索 forest。
$ dotnet add package Lucene.Net.QueryParser --prerelease
我们需要添加 Lucene.Net.QueryParser 包。
using Lucene.Net.Analysis.Standard;
using Lucene.Net.QueryParsers.Classic;
using Lucene.Net.Documents;
using Lucene.Net.Index;
using Lucene.Net.Search;
using Lucene.Net.Store;
using Lucene.Net.Util;
const LuceneVersion ver = LuceneVersion.LUCENE_48;
using var ramDir = new RAMDirectory();
using var analyzer = new StandardAnalyzer(ver);
var idxCfg = new IndexWriterConfig(ver, analyzer);
idxCfg.OpenMode = OpenMode.CREATE;
using var writer = new IndexWriter(ramDir, idxCfg);
createIndex(writer);
doSearch(writer);
void createIndex(IndexWriter writer)
{
var doc = new Document();
doc.Add(new TextField("phrase", "in the sun", Field.Store.YES));
writer.AddDocument(doc);
doc = new Document();
doc.Add(new TextField("phrase", "before midnight", Field.Store.YES));
writer.AddDocument(doc);
doc = new Document();
doc.Add(new TextField("phrase", "a cloud in the sky", Field.Store.YES));
writer.AddDocument(doc);
doc = new Document();
doc.Add(new TextField("phrase", "stormy clouds", Field.Store.YES));
writer.AddDocument(doc);
writer.Commit();
}
void doSearch(IndexWriter writer)
{
using DirectoryReader reader = writer.GetReader(applyAllDeletes: true);
var searcher = new IndexSearcher(reader);
var parser = new QueryParser(ver, "phrase", analyzer);
var query = parser.Parse("cloud OR midnight");
TopDocs topDocs = searcher.Search(query, n: 4);
int hits = topDocs.TotalHits;
Console.WriteLine($"Matching results: {hits}");
foreach (var sdoc in topDocs.ScoreDocs)
{
Document mdoc = searcher.Doc(sdoc.Doc);
Console.WriteLine(mdoc.Get("phrase"));
}
}
该示例使用 QueryParser 搜索两个术语。
void createIndex(IndexWriter writer)
{
var doc = new Document();
doc.Add(new TextField("phrase", "in the sun", Field.Store.YES));
writer.AddDocument(doc);
doc = new Document();
doc.Add(new TextField("phrase", "before midnight", Field.Store.YES));
writer.AddDocument(doc);
doc = new Document();
doc.Add(new TextField("phrase", "a cloud in the sky", Field.Store.YES));
writer.AddDocument(doc);
doc = new Document();
doc.Add(new TextField("phrase", "stormy clouds", Field.Store.YES));
writer.AddDocument(doc);
writer.Commit();
}
我们创建一个包含四个短语的索引。
var parser = new QueryParser(ver, "phrase", analyzer);
var query = parser.Parse("cloud OR midnight");
我们搜索包含术语 cloud 或 midnight 的短语。
$ dotnet run Matching results: 2 before midnight a cloud in the sky
Lucene.Net WildcardQuery
WildcardQuery 实现通配符搜索查询。 它支持 *(匹配任何字符序列,包括空序列)和 ?(匹配任何单个字符)通配符。
John Doe, gardener Roger Roe, driver Patrick Mark, writer Lucy Smith, teacher
users.txt 文件中有四个用户。
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Documents;
using Lucene.Net.Index;
using Lucene.Net.Search;
using Lucene.Net.Store;
using Lucene.Net.Util;
const LuceneVersion ver = LuceneVersion.LUCENE_48;
using var ramDir = new RAMDirectory();
using var analyzer = new StandardAnalyzer(ver);
var idxCfg = new IndexWriterConfig(ver, analyzer);
idxCfg.OpenMode = OpenMode.CREATE;
using var writer = new IndexWriter(ramDir, idxCfg);
List&t;User> users = readUsers();
createIndex(writer, users);
doSearch(writer);
Listlt;User> readUsers()
{
var data = File.ReadAllLines("doc/users.txt");
var users = new List<User>();
foreach (var e in data)
{
var u = e.Split(",");
users.Add(new User(u[0], u[1]));
}
return users;
}
void createIndex(IndexWriter writer, List<User> users)
{
foreach (var user in users)
{
var doc = new Document();
doc.Add(new TextField("name", user.Name, Field.Store.YES));
doc.Add(new TextField("occupation", user.Occupation, Field.Store.YES));
writer.AddDocument(doc);
}
writer.Commit();
}
void doSearch(IndexWriter writer)
{
using DirectoryReader reader = writer.GetReader(applyAllDeletes: true);
var searcher = new IndexSearcher(reader);
var query = new WildcardQuery(new Term("name", "*oe"));
TopDocs topDocs = searcher.Search(query, n: 3);
int hits = topDocs.TotalHits;
Console.WriteLine($"Matching results: {hits}");
foreach (var sdoc in topDocs.ScoreDocs)
{
Document mdoc = searcher.Doc(sdoc.Doc);
Console.WriteLine($"{mdoc.Get("name")} {mdoc.Get("occupation")}");
}
}
record User(string Name, string Occupation);
该示例从文本文件构建用户索引。 然后,它对索引执行通配符查询。
Listlt;User> readUsers()
{
var data = File.ReadAllLines("doc/users.txt");
var users = new List<User>();
foreach (var e in data)
{
var u = e.Split(",");
users.Add(new User(u[0], u[1]));
}
return users;
}
我们从 doc/users.txt 文件读取数据并创建 User 对象列表。
void createIndex(IndexWriter writer, List<User> users)
{
foreach (var user in users)
{
var doc = new Document();
doc.Add(new TextField("name", user.Name, Field.Store.YES));
doc.Add(new TextField("occupation", user.Occupation, Field.Store.YES));
writer.AddDocument(doc);
}
writer.Commit();
}
创建一个索引。 每个文档都有 name 和 occupation 字段。
var query = new WildcardQuery(new Term("name", "*oe"));
TopDocs topDocs = searcher.Search(query, n: 3);
WildcardQuery 查找 name 字段。 我们寻找具有 oe 后缀的名称。
$ dotnet run Matching results: 2 John Doe gardener Roger Roe driver
Lucene.Net RegexpQuery
RegexpQuery 允许我们创建利用正则表达式的 Lucene 查询。
sky blue rock war
这是 words.txt 文件。
forest cup cloud pen wrong
这是 words2.txt 文件。
new balloon book wood page
这是 words3.txt 文件。
这三个文本文件用于构建我们的搜索索引。
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Documents;
using Lucene.Net.Index;
using Lucene.Net.Search;
using Lucene.Net.Store;
using Lucene.Net.Util;
const LuceneVersion ver = LuceneVersion.LUCENE_48;
using var ramDir = new RAMDirectory();
using var analyzer = new StandardAnalyzer(ver);
var idxCfg = new IndexWriterConfig(ver, analyzer);
idxCfg.OpenMode = OpenMode.CREATE;
using var writer = new IndexWriter(ramDir, idxCfg);
var words = new List<string>();
readWords(words);
createIndex(writer, words);
doSearch(writer);
// read words from files
void readWords(List<string> words)
{
string[] files = System.IO.Directory.GetFileSystemEntries("doc", "*.txt");
foreach (var fname in files)
{
string[] lines = File.ReadAllLines(fname);
words.AddRange(lines);
}
}
// create index from words
void createIndex(IndexWriter writer, List<string> words)
{
foreach (var word in words)
{
var doc = new Document();
doc.Add(new TextField("word", word, Field.Store.YES));
writer.AddDocument(doc);
}
writer.Commit();
}
// do regex search
void doSearch(IndexWriter writer)
{
using DirectoryReader reader = writer.GetReader(applyAllDeletes: true);
var searcher = new IndexSearcher(reader);
var rx = "[wc].*";
var query = new RegexpQuery(new Term("word", rx));
TopDocs topDocs = searcher.Search(query, n: 10);
int hits = topDocs.TotalHits;
Console.WriteLine($"Matching results: {hits}");
foreach (var sdoc in topDocs.ScoreDocs)
{
Document mdoc = searcher.Doc(sdoc.Doc);
Console.WriteLine(mdoc.Get("word"));
}
}
在程序中,我们从文本文件中的单词构建索引。 我们对索引应用正则表达式查询搜索。
void readWords(List<string> words)
{
string[] files = System.IO.Directory.GetFileSystemEntries("doc", "*.txt");
foreach (var fname in files)
{
string[] lines = File.ReadAllLines(fname);
words.AddRange(lines);
}
}
我们从文本文件读取数据并将单词存储在 words 列表中。
void createIndex(IndexWriter writer, List<string> words)
{
foreach (var word in words)
{
var doc = new Document();
doc.Add(new TextField("word", word, Field.Store.YES));
writer.AddDocument(doc);
}
writer.Commit();
}
我们从单词构建索引。
void doSearch(IndexWriter writer)
{
using DirectoryReader reader = writer.GetReader(applyAllDeletes: true);
var searcher = new IndexSearcher(reader);
...
}
我们在 doSearch 函数中执行搜索。
var rx = "[wc].*";
var query = new RegexpQuery(new Term("word", rx));
TopDocs topDocs = searcher.Search(query, n: 10);
我们的查询是 RegexpQuery。 正则表达式匹配所有以字符 w 或 c 开头的单词。
$ dotnet run Matching results: 5 war cup cloud wrong wood
来源
在本文中,我们使用了 Lucene.Net 库。
作者
列出所有 C# 教程。