浅谈lucene

本文深入剖析Lucene全文检索框架,涵盖其基本概念、工作原理、架构组成及核心技术,如Analyzer、Document、Field、Term、Token等,同时提供实例代码,展示索引创建与查询过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.lucene简介
Lucene是一个全文搜索框架,而不是应用产品用,它只是提供了一种工具让你能实现例如百度、谷歌等搜索产品。lucene的功能很单一,即提供一个全文搜索服务,通过用户传递的若干字符串,告诉用户搜索的关键词出现在哪。
2.lucene术语概念简介
2.1 Analyzer
Analyzer为分析器,能把一个字符串按照某种规则划分为一个个词语,并去除其中的无效词语,无效词语指的是英文中的“of”、“the”,中文里的“的”、“地”等词语,因为这些词语通常在文中大量出现但却没啥关键信息,去掉后有利于缩小索引文件、提高效率及命中率。分词规则很多,核心在于按语义划分。由于英语本身已经按照空格分隔,所以容易划分。而中文语义博大精深,同一句话不同的分隔方式能有不同的意思,所以不同的分词方法能得到不同的结果,做好中文的分词并不容易。
2.2 Document
用户提供的源数据可以为文本文件、字符串、数据库表中的一条记录等等。一条记录处理后变为一个Document,存入索引文件。同样当用户索引时,以Document的形式返回。
2.3 Field
一个Document包含若干信息域,这些信息域通过Field存储在Document里。Field有四个属性:列名、值、存储和索引。其中存储和索引为可选属性。存储属性能让用户决定是否对值进行存储,索引属性让用户决定是否对值进行索引以及是否分词。
2.4 Term
Term是搜索的最小单位,它表示文档的一个词语,Term由两部分组成:它表示的词语和这个词语所出现的Field。
2.5 Token
Token是Term的一次出现,它包含Term文本和相应的起止偏移,以及一个类型字符串。一句话中可以出现多次相同的词语,它们都用同一个Term表示,但是用不同的Token,每个Token标记该词语出现的地方。
2.6 Segment
添加索引时并不是每个Document马上添加到同一个索引文件,它们首先被写入到不同的小文件,然后再合并成一个大索引文件,这里每个小文件就是一个Segment。

3.lucene的工作方式
lucene提供两部分的服务:(1)写入:将用户提供的源(字符串)写入索引或将源从索引中删除。(2)读出:向用户提供全文搜索服务,让用户通过关键词定位源。
3.1写入流程
通过将每一条数据进行设置,放入Document ,再由 IndexWriter(索引写入器)写入建立索引。

        /// <summary>
        /// 初始化索引
        /// </summary>
        public static void InitIndex()
        {
            List<Commodity> commodityList = GetList();

            FSDirectory directory = FSDirectory.Open(StaticConstant.TestIndexPath);//文件夹
            using (IndexWriter writer = new IndexWriter(directory, new PanGuAnalyzer(), true, IndexWriter.MaxFieldLength.LIMITED))//索引写入器
            {
                foreach (Commodity commdity in commodityList)
                {
                    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 ", Field.Store.YES, Field.Index.ANALYZED));
                    doc.Add(new NumericField("price", Field.Store.YES, true).SetDoubleValue((double)(commdity.Price)));
                    //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"))));
                    writer.AddDocument(doc);//写进去
                }
                writer.Optimize();//优化  就是合并(将存入路径下的各个小文件进行合并)
            }
        }

3.2读出流程
用户提供搜索关键词,选择适当的analyzer进行处理,搜索索引找出相应的document,用户根据需求找出需要的Field。

        public static void Show()
        {
            FSDirectory dir = FSDirectory.Open(StaticConstant.TestIndexPath);
            IndexSearcher searcher = new IndexSearcher(dir);//查找器
            {
                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(Lucene.Net.Util.Version.LUCENE_30, "title", new PanGuAnalyzer());//解析器
            {
                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", 20180000, 20181822, true, true);//过滤
                    SortField sortPrice = new SortField("price", SortField.DOUBLE, false);//降序
                    SortField sortTime = new SortField("time", SortField.INT, 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);
                }
            }
        }

另外在使用filter时请注意:filter的作用就是限制只查询索引的某个子集,它的作用有点像SQL语句里的 where,但又有区别,它不是正规查询的一部分,只是对数据源进行预处理,然后交给查询语句。注意它执行的是预处理,而不是对查询结果进行过滤,所以使用filter的代价是很大的,它可能会使一次查询耗时提高一百倍。
最常用的filter是RangeFilter和QueryFilter。RangeFilter是设定只搜索指定范围内的索引;QueryFilter是在上次查询的结果中搜索。

4.lucene结构
lucene包括core和sandbox两部分,其中core是lucene稳定的核心部分,sandbox包含了一些附加功能,例如highlighter、各种分析器。
lucene core有七个包:analysis,document.index,queryParser,search,store,util。
4.1 analysis
Analysis包含一些内建的分析器,例如按空白字符分词的WhitespaceAnalyzer,添加了stopwrod过滤的StopAnalyzer,最常用的StandardAnalyzer。
4.2 document
Document.含文档的数据结构,例如document.定义了存储文档的数据结构,Field类定义了document.一个域。
4.3 index
Index包含了索引的读写类,例如对索引文件的segment进行写、合并、优化的IndexWriter类和对索引进行读取和删除操作的 IndexReader类,这里要注意的是不要被IndexReader这个名字误导,以为它是索引文件的读取类,实际上删除索引也是由它完成, IndexWriter只关心如何将索引写入一个个segment,并将它们合并优化;IndexReader则关注索引文件中各个文档的组织形式。
4.4 queryParser
QueryParser包含了解析查询语句的类,lucene的查询语句和sql语句有点类似,有各种保留字,按照一定的语法可以组成各种查询。 Lucene有很多种Query类,它们都继承自Query,执行各种特殊的查询,QueryParser的作用就是解析查询语句,按顺序调用各种 Query类查找出结果。
lucene提供了一种类似于SQL语句的查询语句,我们姑且叫它lucene语句,通过它,你可以把各种查询一句话搞定,lucene会自动把它们查分成小块交给相应Query执行。下面我们对应每种 Query演示一下:
TermQuery可以用“field:key”方式,例如“content:lucene”。
BooleanQuery中‘与’用‘+’,‘或’用‘ ’,例如“content:java content:perl”。
“content:java + content:perl”
WildcardQuery仍然用‘?’和‘*’,例如“content:use**”。
PhraseQuery用‘~’,例如“content:wuzza ~5“。
PrefixQuery用‘**’,例如“中 *”。
FuzzyQuery用‘~’,例如“content: wuzza ~”。
RangeQuery用‘[]’或‘{}’,前者表示闭区间,后者表示开区间,例如“time:[20060101 TO 20060130]”,注意TO区分大小写。
你可以任意组合query string,完成复杂操作,例如“标题或正文包括lucene,并且时间在20060101到20060130之间的文章” 可以表示为:“+ (title:lucene content:lucene) +time:[20060101 TO 20060130]”。

Directory dir = FSDirectory.getDirectory(PATH, false);
IndexSearcher is = new IndexSearcher(dir);
QueryParser parser = new QueryParser("content", new StandardAnalyzer());
Query query = parser.parse("+(title:lucene content:lucene) +time:[20060101 TO 20060130]");
Hits hits = is.search(query);
for (int i = 0; i < hits.length(); i++)
{
document.doc = hits.doc(i);
System.out.println(doc.get("title");
}
is.close();

4.5 search
Search包含了从索引中搜索结果的各种类,例如刚才说的各种Query类,包括TermQuery、BooleanQuery等就在这个包里。
4.5.1 TermQuery
首先介绍最基本的查询,如果你想执行一个这样的查询:“在content域中包含‘lucene’的document.rdquo;,那么你可以用TermQuery:

Term t = new Term(“content”, " lucene");
Query query = new TermQuery(t);

4.5.2 BooleanQuery
如果你想这么查询:“在content域中包含java或perl的document.rdquo;,那么你可以建立两个TermQuery并把它们用BooleanQuery连接起来:

TermQuery termQuery1 = new TermQuery(new Term(“content”, “java”);
TermQuery termQuery 2 = new TermQuery(new Term(“content”, “perl”);
BooleanQuery booleanQuery = new BooleanQuery();
booleanQuery.add(termQuery 1, BooleanClause.Occur.SHOULD);
booleanQuery.add(termQuery 2, BooleanClause.Occur.SHOULD);

4.5.3 WildcardQuery
如果你想对某单词进行通配符查询,你可以用WildcardQuery,通配符包括’?’匹配一个任意字符和’*'匹配零个或多个任意字符,例如你搜索’'use*’,你可能找到’useful’或者’useless’:

Query query = new WildcardQuery(new Term(“content”, “use*”);

4.5.4 PhraseQuery
你可能对中日关系比较感兴趣,想查找‘中’和‘日’挨得比较近(5个字的距离内)的文章,超过这个距离的不予考虑,你可以:

PhraseQuery query = new PhraseQuery();
query.setSlop(5);
query.add(new Term("content ", “中”));
query.add(new Term(“content”, “日”));

那么它可能搜到“中日合作……”、“中方和日方……”,但是搜不到“中国某高层领导说日本欠扁”。

4.5.5 PrefixQuery
如果你想搜以‘中’开头的词语,你可以用PrefixQuery:

PrefixQuery query = new PrefixQuery(new Term("content ", “中”);

4.5.6 FuzzyQuery
FuzzyQuery用来搜索相似的term,使用Levenshtein算法。假设你想搜索跟‘wuzza’相似的词语,你可以:

Query query = new FuzzyQuery(new Term(“content”, “wuzza”);

你可能得到‘fuzzy’和‘wuzzy’。

4.5.7 RangeQuery
另一个常用的Query是RangeQuery,你也许想搜索时间域从20060101到20060130之间的document.你可以用RangeQuery:

RangeQuery query = new RangeQuery(new Term(“time”, “20060101”), new Term(“time”, “20060130”), true);

最后的true表示用闭合区间。

4.6 store
Store包含了索引的存储类,例如Directory定义了索引文件的存储结构,FSDirectory为存储在文件中的索引,RAMDirectory为存储在内存中的索引,MmapDirectory为使用内存映射的索引。
4.7 util
Util包含一些公共工具类,例如时间和字符串之间的转换工具。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值