Lucene实现搜索
一 LUCENE介绍:
1、全文索引:
数据分为两种:结构化数据和非结构化数据,结构化数据:指具有固定格式或有限长度的数据,如数据库,元数据等。非结构化数据:指不定长或无固定格式的数据,如邮件,word文档等。对结构化数据的搜索:如对数据库的搜索,用SQL语句。再如对元数据的搜索,如利用windows搜索对文件名,类型,修改时间进行搜索等。对非结构化数据的搜索:如利用windows的搜索也可以搜索文件内容,再如用Google和百度可以搜索大量内容数据。对于数据搜索方式一种是顺序搜索,即一个文件一个文件的搜索。如果是结构化数据则会相对较快(由于结构化数据有一定的结构可以采取一定的搜索算法加快速度),而非结构话数据就比较缓慢了。那如果我们采用一种方式将非结构化数据变为结构化数据再搜索势必会提高搜索效率。这就是全文检索的基本思路,也即将非结构化数据中的一部分信息提取出来,重新组织,使其变得有一定结构,然后对此有一定结构的数据进行搜索,从而达到搜索相对较快的目的。这部分从非结构化数据中提取出的然后重新组织的信息,我们称之索引。这种先建立索引,再对索引进行搜索的过程就叫全文检索(Full-text Search)
2.倒排索引:
倒排索引[2] (英语:Inverted index),也常被称为反向索引、置入档案或反向档案,是一种索引方法,被用来存储在全文搜索下某个单词在一个文档或者一组文档中的存储位置的映射。它是文档检索系统中最常用的数据结构。通过倒排索引,可以根据单词快速获取包含这个单词的文档列表。字符串到文档的映射:Term:{doc1,doc2,doc3}这种形式。
倒排索引项:
每个文档会记录文档编号(DocID),单词在这个文档中出现的次数(TF)及单词在文档中哪些位置出现过等信息,这样与一个文档相关的信息被称做倒排索引项(Posting)。图中这3个都是倒排索引项
假设我的文档集合里面有100篇文档,为了方便表示,我们为文档编号从1到100,得到下面的结构
左边保存的是一系列字符串,称为词典。每个字符串都指向包含此字符串的文档(Document)链表,此文档链表称为倒排表(Posting List)。
有了索引,便使保存的信息和要搜索的信息一致,可以大大加快搜索的速度。
比如说,我们要寻找既包含字符串“lucene”又包含字符串“solr”的文档,我们只需要以下几步:
(1). 取出包含字符串“lucene”的文档链表。
(2). 取出包含字符串“solr”的文档链表。
(3). 通过合并链表,找出既包含“lucene”又包含“solr”的文件。
3、Lucene是apache软件基金会4 jakarta项目组的一个子项目,是一个开放源代码的全文检索引擎工具包,即它不是一个完整的全文检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎,在Java开发环境里Lucene是一个成熟的免费开源工具。
lucene java源码学习和下载地址:http://lucene.apache.org/
Lucene.net 源码学习和下载地址:http://lucenenet.apache.org/
4、Lucene除了自己基础的程序包以外还有很多 捐赠模块组件。例如相似度查询、高亮显示、多语言分析器等。这些都可以在http://lucene.apache.org/找到源代码。在Contrib目录下
.net使用lucene以及这些捐赠模块组件的时候可以通过Nuget直接下载使用。lucene基础包搜索 Lucene.Net,捐赠模块扩展包搜索Lucene.Net.Contrib
5、Lucene 关于价格类型(Decaminl、Double、Float)区间搜索的问题
在采用RangeFilter过滤器进行价格区间搜索时,Lucene是将字段值都作为字符串,并按照字典顺序对字符串进行排序(当然排序还会对小数点进行排序);这就会导致你想搜索10到30的商品 会搜索到 10.0,13.0,200.0,31.2的结果出来。200.0显然不是我们想要的;解决的办法是修改索引补0;
创建索引时对索引中的价格域进行往后去除小数点,往前补0达到规定的位数。例如我们的价格是精确到小数点3位的,且价格在10位以内的,那就先 price*1000 去除小数点,然后_price.PadLeft(10, '0'); 在价格前补0 一直到10位为止;
结果: 123变为 0000012300
123.5 变为 0000012350
123.55变为0000012355
如果需要从索引中显示价格,我们需要先将去除的价格转化为Float类型 然后除以10(或100)
6、Lucene的评分
Lucene搜索结果是根据查询相关度和文档对象来排序的,每个稳定都会有一个评分;评分越高显示越靠前。lucene的评分由多个因子影响,索引时的加权因子是其中一个;其他还有文档中搜索项出现的频率,搜索项在倒排索引里出现的频率、查询标准因子、
(1)Lucene加权因子说明:
1、在创建索引时可以针对单个Document设置加权因子.默认值是1.0
Doc.Setboost(1.5) 将Document加权因子设置为1.5,它会隐式的将所有field域的出水加权因子设为指定值1.5(默认也是1.0)
2、 也可以对单个field域设置加权因子,例如:Field.Setboost(1.2);如果在同一个文档中,多次将同一个域加入到该文档,那么该域的加权因子就等于该域在整个文档中加权因子之和
(2)Lucene 查询标准因子说明:
Query对象本身对匹配文档的评分也有影响,仅在多重子句查询时有效(布尔查询)。如果只搜索单个项,是对所有文档进行相同比例的加权。
在多重子句查询时一些文档可能只匹配其中一个子句;使用不同的查询因子可以区分不同的查询条件,默认也是1.0
(3)其他的如:tf(t in d) 项频率因子——文档(d)中出现项(t)的频率 ,
idf(t) 项在倒排文档中出现的频率:它被用来衡量项的“唯一”性.出现频率较高的term具有较低的idf,出现较少的term具有较高的idf
关于评分更详细的介绍:http://www.hankcs.com/program/java/lucene-scoring-algorithm-explained.html
7、Query查询
Query类 封装查询的及其子类,它的实例将会作为IndexSearcher对象 search方法的参数;
TermQuery Query的子类,构造方法允许一个单独的Term对象作为参数,对特定项进行搜索(特定域,特定字段。指Term)
==>类似于new QueryParser(Lucene.Net.Util.Version.LUCENE_30, "Title", standardAnalyzer).Parse("querystr")-->"field":"value"
TermRangeQuery 指定范围内搜索
NumericRangeQuery 指定数字范围内搜索
PrefixRangeQuery 通过字符串搜索(前缀)
BooleanQuery 组合查询
PhraseQuery 短语搜索
WildcardQuery 通配符查询
例:使用Query子类构造查询条件:
BooleanQuery boolquery = new BooleanQuery();//组合查询条件
boolquery.Add(new TermQuery(new Term("website", "beijingzhan")), Occur.SHOULD);//单值域条件
boolquery.Add(new WildcardQuery(new Term("Keywords", "*查询字符串*")), Occur.SHOULD);//通配符查询条件
//多条件匹配,且指定每个条件的查询因子
Dictionary<string, float> dic = new Dictionary<string, float>();
dic.Add("title", (float)0.8);
dic.Add("summary", (float)0.5);
MultiFieldQueryParser multiParser = new MultiFieldQueryParser(Lucene.Net.Util.Version.LUCENE_30, new string[] { "title", "summary" }, standardAnalyzer, dic);
multiParser.DefaultOperator = QueryParser.Operator.AND; //设置分词后的短语的匹配关系,默认为或的关系; 例如:或{((title:智利^0.8 summary:智利^0.5) (title:进口^0.8 summary:进口^0.5) (title:蓝莓^0.8 summary:蓝莓^0.5))}
boolquery.Add(multiParser.Parse(queryStr), Occur.SHOULD);//设置 多关键词匹配 跟其他条件之间的关系是 或
上面构造出的查询条件:{website:beijingzhan Keywords:*查询字符串* (+(title:智利^0.8 summary:智利^0.5) +(title:进口^0.8 summary:进口^0.5) +(title:蓝莓^0.8 summary:蓝莓^0.5))}
multiParser.DefaultOperator = QueryParser.Operator.AND; 设置说明:如果搜索智利进口蓝莓,假设分词结果是: 智利 进口 蓝莓 如果不设置该项或者设置为or 那么他会匹配所有包含 智利 或者包含进口 或者包含 蓝莓的商品。如果设置为and 那么它就只会匹配来自智利的进口蓝莓;title或sumary里必须包含智利 进口 蓝莓 字样;当然title里包含智利 summary里包含进口蓝莓 也会被匹配上。
+ 告诉lucene内容必须匹配上文档才认为该文档被命中 - 相反,给定内容不能再文档中出现:例如我们要查询name字段中包含 “水” 并且destion字段中不包含“打折”的文档: +name:水 -destion打折
QueryParse类 将用户输入的查询字符串转化为query对象,它可以创建前面所有的查询子类(以下是parse方法里的表达式格式)
默认的域名在创建QueryParse时提供,但搜索时不局限于这个默认的域
1)范围查询 .parse("域名":[A TO C]) 查询范围的两端用大写TO连接,起点或终点本身([]表示包含在内,{}表示排除在外).查询两端如果没有空格的话要加双引号
2)前缀查询通配符查询 .parse("域名":"abc*") 如果某个项包含了?或*就会被看做是通配符查询对象WildcardQuery,而查询项只在末尾有个*,它就是前缀查询对象PrefixRangeQuery
3)布尔查询 .parse("域名":a AND b) .parse("域名":a AND NOT b) 操作符AND、OR、NOT必须是大写 NOT b指不包含b(也可以任意组合.parse("域名":(a OR c) AND b))
4)短语查询 注意,双引号内的文本会被分析器排除停用词分析成短语,转换为PhraseQuery短语搜索
5)模糊查询 .parse("abcdef~0.6") 双引号是短语查询,后面跟的波浪线可以指定最小相似度。浮点型的
6)*:*查询所有
7)^加权
例:使用QueryParse类构造查询条件
string queryStr="查询字符串";
QueryParser parse = new QueryParser(Lucene.Net.Util.Version.LUCENE_30, "Title", new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_30)); //针对title域进行匹配 Query query = parse.Parse(queryStr);
针对多个域的一次性查询:
用户搜索时,只会在特殊情况下单独搜索某个域,一般情况下,都会针对多个域进行搜索。实现方式有3中:
(1)创建多值域(全包含域)进行索引,每个值之间空格风格。缺陷是你不能对每个域进行加权操作
(2)使用MultiFieldQueryParser QueryParser的子类.可以对每个域实现加权
(3) DisjunceionMaxQuery 它会封装一个或多个查询,将匹配的文档进行or操作
Query query = MultiFieldQueryParser.Parse(Lucene.Net.Util.Version.LUCENE_30,
"查询字符串",
new string[] { "Name", "Title" },//匹配的条件
new Occur[] { Occur.MUST, Occur.MUST },//条件之间的关系
new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_30));//分析器,可以指定自定义分析器
8、 sort排序:
(1)Lucene按照查询相关度进行排序这也成为默认的评分方式,匹配到的文档都是评分大于0的文档。影响排序的因素:创建索引时指定的权重,搜索时针对条件域设置的查询因子,排序时的sort条件
(2)sort实例内,保存了一个SortField数组public Sort(SortField field),每个SortField对象都包含 (域名 域类型 反向排序方式) 3个参数
SortField还包含SCORE DOC STRING BYTE INT LONG FLOAT DOUBLE等类型。(SCORE针对相关性排序的特殊类型,DOC是针对文档id排序的特殊类型 STRING 字母顺序升序排列)
(3)sort有两个静态对象:Sort.RELEVANCE 按照相关性排序 Sort.INDEXORDER 按照索引顺序排序 返回的都是sort对象
开发人员也可以自定义自己的域排序:new Sort(new SortField("域1", SortField.STRING))或者new Sort(new SortField("域2", SortField.INT,true))
也可以一次性指定:new Sort(new SortField("域1", SortField.STRING), SortField.FIELD_SCORE, new SortField("域2", SortField.INT, true)) //组合排序 首先根据域1排序(升序),然后按照相关性排序, 最后根据域2降序排序
(4)生成的Sort排序条件:{<auto: "saleCount">!,<auto: "onlineTime">!,<score>,<auto: "sortNum">} 带!为降序,按照saleCount降序 onlineTime降序,评分排序,sortNum升序
注意:lucene用于排序的field的值不能是null或者sting.empty ,因为lucene会将排序field的值转成int型,转换失败时会报如下错误:
System.IndexOutOfRangeException: 索引超出了数组界限。
在 Lucene.Net.Search.AnonymousClassIntParser1.ParseInt(String val)
在 Lucene.Net.Search.FieldCacheImpl.IntCache.CreateValue(IndexReader reader, Entry entryKey)
在 Lucene.Net.Search.FieldCacheImpl.Cache.Get(IndexReader reader, Entry key)
在 Lucene.Net.Search.FieldCacheImpl.GetInts(IndexReader reader, String field, IntParser parser)
在 Lucene.Net.Search.FieldCacheImpl.IntCache.CreateValue(IndexReader reader, Entry entryKey)
在 Lucene.Net.Search.FieldCacheImpl.Cache.Get(IndexReader reader, Entry key)
在 Lucene.Net.Search.FieldCacheImpl.GetInts(IndexReader reader, String field, IntParser parser)
在 Lucene.Net.Search.FieldCacheImpl.GetInts(IndexReader reader, String field)
9、Filter
//针对价格的补位操作,无论是排序还是过滤,数字如果不指定类型默认按照字符串的顺序排列。
string startPrice = ((Int64)(requestModel.startPrice * 1000)).ToString();
string endPrice = ((Int64)(requestModel.endPrice * 1000)).ToString();
startPrice = startPrice.PadLeft(10, '0');
endPrice = endPrice.PadLeft(10, '0');
Filter filter = new TermRangeFilter("price", startPrice, endPrice,true, true);//后面两个参数是是否包含指定数字
10、Lucene的分析器
(1)Analysis分析器,在lucene中指将Field域文本转换为最基本的索引表示单元Term项的过程,在搜索时这些项决定了什么样的文本能匹配到查询条件。
(2)语汇单元化:分析器对分析操作做了封装,分析操作可能包括:去除标点符号、音调符号、特殊字符,提取单词、移除常用词、转为小写等。而分析后文本流变成了最终的文本块,而将文本块跟field域名结合到一起就是项Term,项构成了搜索的基础条件。此Term就是用于搜索时匹配的项。
(3)Lucene有多种分析器,分析后的结果不一样将会导致搜索结果的不一样;
(4)在索引时和搜索时都会用到分析器:索引时在创建文档时将会指定field是否进行分析处理,分析后将会成为term作为搜索匹配项。搜索时也需要对搜索关键词进行分析,最终成为term。与索引中的term进行匹配。
(5)进行查询时,QueryParse使用的分析器和索引时的分析器不一定非要相同。这以情况而定,普遍情况下使用相同的比较好。但是在使用更复杂的分析器时,索引和查询的不同效果更好。
语汇单元:在索引时lucene使用特定的分析器来处理需要被语汇单元化的特定域,并且将每个语汇单元以项的形式写入索引中。多个语汇单元组成语汇单元流;语汇单元和索引中的项的关系,每个语汇单元都作为一个项传递给索引,位置增量是语汇单元携带到索引中的唯一附加元数据
(6)查询时KeywordAnalyzer分析器是将整个字符流作为一个单独的语汇单元;即不分词。
(7)PerFieldAnalyzerWrapper为不同域指定不同分析器的方法:
//查询时为特定域指定特定分析器的方法:首先指定默认的分析器StandardAnalyzer
PerFieldAnalyzerWrapper analyzer = new PerFieldAnalyzerWrapper(new StandardAnalyzer());
analyzer.AddAnalyzer("productname", new KeywordAnalyzer());
Query query=QueryParse.parse("productname","descripty",analyzer);
这样在productname域上使用的就是KeywordAnalyzer分析器,其他的就是StandardAnalyzer分析器;
注意在创建索引时也需要为指定的域指定为只索引不分词: Field.Index.NOT_ANALYZED
fieldKeyword = new Field("keywords","牛奶" , Field.Store.YES, Field.Index.NOT_ANALYZED, Field.TermVector.WITH_OFFSETS);
关于此处设计时,可以将数据库的keywords字段的值约定为: 牛奶,鲜奶,无脂牛奶 这种格式的;在创建索引时以逗号分隔关键词,为每一个关键词创建出一个field;例如此处就会有3个field ;当关键词很多时我们也可以约定只使用前10个关键词作为只索引不分词的field;在查询时为这些field指定单独KeywordAnalyzer的分析器;全字匹配他们;
WhitespaceAnalyzer 通过空格来分隔文本信息
SimpleAnalyzer 通过非字母符来分隔文本信息,去除数字字符,统一转为小写
StopAnalyzer 它会去除常用词(the is..),其他与SimpleAnalyzer一样
StandardAnalyzer lucene最为复杂的分析器,他会识别注入公司名称,email地址,ip地址。还会转为小写,去除停用词,标点
它也是lucene内置的唯一能处理亚洲雨中的分析器,能够将一定范围内的Unicode编码识别为CJK字符(中国、日本、韩国)
默认的standAnalyzer分析器会将关键词分为 :三国演义贴吧——》三 国 演 义 贴 吧;分析后所产生的查询条件为 {+title:"三 国 演 义 贴 吧"}:只有所有满足条件的才会显示出来;此时title包含三国演义贴吧就会被命中,但是只包含三国的不会被命中;如果此时搜索的是“三国”-》“三 国” 只有title里包含三国的会被命中;即:standAnalyzer不会对搜索关键词进行分词,拆次的操作。由此看来我们需要在构造query对象之前对搜索关键词进行分词,例如使用盘古分词
Lucene的Contrib目录中3个分析器适合处理亚洲语种。CJKAnalyzer ChineseAnalyzer smart ChineseAnalyzer
11 lucene的核心类
(1)IndexSearcher 以只读方式打开索引的类,连接索引的中心环节,父类search。他需要通过提供Directory存储区参数来访问创建的索引
创建IndexSearcher对象可以使用IndexReader与索引交互,也可以从索引目录直接创建(这时候lucene会自动创建私有的IndexReader)
(2)IndexReader 打开所有索引文件和提供底层reader API的工,打开IndexReader需要较大的系统开销,因此在搜索期间都重复使用同一个IndexReader实例
创建IndexReader时,他会搜索已有的索引快照,如果搜索索引的变更信息需要打开一个新的reader。可以通过IndexReader.reOpern()方法获取新的reader对象(注意此时要关闭旧的IndexReader)
(3)Term 搜索的基本单元,每个Term包含一个域名和文本值并和termquery一起使用
(4)Directory 描述了lucene索引的存放位置,它是一个抽象类,他的子类负责制定索引的具体存储路径。
FSDirectory 真实文件在文件系统中的存储路径(SimpleFSDirectory不能很好的支持多线程)
RAMDirectory 索引存储于内存
MMapDirectory 使用映射内存io进行文件访问
FileSwitchDirectory,使用两个文件目录,根据文件扩展名在两个目录之间切换
我们可以使用FSDirectory.open()方法来自动选择合适的Directory子类
(5) Document 一个或多个Field域的集合,相当于数据表中的一条记录,而每一个Field域相当于一条记录的字段。
每一个Document都是独立无关联的,他是搜索和索引的最小原子单位
(6) Field 该类在事实上控制着被索引的域值,当创建好一个域时你可以指定多个选项来控制lucene将文档添加到索引后 对该域的操作
1) 域索引选项 Field.Index 通过倒排索引,来控制域是否被搜索
Field.Index.ANALYZED 使用分析器将值分词,使每个词都能被搜索。适用于普通文本域
Field.Index.ANALYZED_NO_NORMS 类似于上面那个,只是不存储norms信息,搜索时可能比较耗费内存
Field.Index.NOT_ANALYZED 对域进行索引,但不对值进行分词,适用于不能被分解的域值,例如url/路径、日期、号码
Field.Index.NOT_ANALYZED_NO_NORMS 不分词,不存储norms信息。节省索引空间,减少内存耗费
Field.Index.NO 对应的域值不被搜索
2) 域存储选项 Field.Store 用来确定是否需要存储域的真实值,以便搜索时恢复
Field.Store.YES 原始字符串值被全部保存在索引中,并可以有IndexReader类恢复。该选项对于需要展示搜索结果的一些域 如url 标题 很有用,但是不要存储过大的值
Field.Store.NO 不存储域值,如web页面的正文等大型文本,这些域值不用恢复 ,虽然lucene提供了一个类可以让你去压缩域值,但是它以牺牲cpu的计算能力为代价来节省磁盘空间。不建议使用CommpressionToos
3) 域的向量选项 它是介于索引域和存储域的一个中间结构
Field.TermVector Field.TermVector.NO表示不索引Token的位置属性
Field.TermVector.WITH_OFFSETS表示额外索引Token的结束点
Field.TermVector.WITH_POSITIONS表示额外索引Token的当前位置
Field.TermVector.WITH_POSITIONS_OFFSETS表示额外索引Token的当前和结束位置
Field.TermVector.YES则表示存储向量
4) 域排序,当搜索时你可能需要排序功能(默认按照Lucene评分)。如果域是数值类型你需要用到NumericField类,如果是文本类型就要保证它不被分析(分词)
NumericField 类,对数字的支持域,还能处理日期和事件
5) 多值域 指同名的不同域 reader 如果在内存中对整个域值进行保存会导致较大开销或者不方便保存是使用此选项
6) 对域和文档加权操作: 可以在创建索引时加权,也可以在搜索时加权
创建索引时加权:
可以对指定文档进行加权,构造好文档后调用doc.setBoost(float f) 方法
当对文档进行加权以后,里面的域使用同一个加权因子。如果我们需要细粒度到域的话可以调用Field类的setBoost(float f) 方法
需要注意的是当你需要改变一个域或者一个文档的加权因子的话,你需要删除并创建对应的文档
默认情况下IndexWrite类会调用Similarity来隐含加权,可以重写它
7) 截取域
IndexWrite允许你对域进行截取后再进行索引,在实例化Indexwrite后向其传入MaxFieldLength,从而向程序传递截取数量(默认1000),建立Indexwrite以后,你也可以调用setMaxFieldLength方法调整成都。但是需要注意的是超出部分将不会被索引,不会被搜索到:
(7)创建IndexWriter对象
* 第一个参数指定了所创建的索引要存放的位置,他可以是一个 File 对象,也可以是一个 FSDirectory 对象或者 RAMDirectory 对象。
* 第二个参数指定了 Analyzer 类的一个实现,也就是指定这个索引是用哪个分词器对文挡内容进行分词。
* 第三个参数指定是否是全新创建索引(还是增量修改索引)
* 第四个参 IndexWriter.MaxFieldLength.UNLIMITED 它指示IndexWriter索引文档中的所有语汇单元
public IndexWriter(Directory d, Analyzer a, bool create, IndexWriter.MaxFieldLength mfl);
创建索引:
string parth = @"D:\Index"; Directory dirctory = FSDirectory.Open(parth); IndexWriter write = new IndexWriter(dirctory, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_30), IndexWriter.MaxFieldLength.UNLIMITED); CreateIndex(write, "小周", "网管", "2014-01-01"); CreateIndex(write, "小朱", "程序员", "2014-01-01"); CreateIndex(write, "小王", "网页设计师", "2014-01-01"); CreateIndex(write, "小沈", "平面设计师", "2014-01-01"); CreateIndex(write, "小范", "程序员", "2014-01-01"); write.Close();
搜索:
//查询条件
Query query = MultiFieldQueryParser.Parse(Lucene.Net.Util.Version.LUCENE_30, "lucene", new string[] { "Name", "Title" }, new Occur[] { Occur.MUST, Occur.MUST }, standardAnalyzer);
//执行查询 string parth = @"D:\Index"; Directory dirctory = FSDirectory.Open(parth); IndexSearcher search = new IndexSearcher(dirctory, true); TopDocs hits = search.Search(query, search.MaxDoc);//第二个参数:获取搜索结果的条数(这里为了取得所有命中的搜索结果,取了索引数据的最大值,索引文件里的数据量) //获取结果 List<string> strlist = new List<string>(); foreach (ScoreDoc n in hits.ScoreDocs) { Document doc = search.Doc(n.Doc); string name = doc.Get("Name"); strlist.Add(name); } return strlist;
使用Collector子类执行搜索,并收集结果集:
Collector两个子类:TopScoreDocCollector 按照默认评分排序收集结果集 TopFieldCollector 按照自定义排序收集结果集,(如果有自定义排序的需求,要使用该类。)
IndexSearcher searcher = new IndexSearcher(directory, false); QueryParser parse = new QueryParser(Lucene.Net.Util.Version.LUCENE_30, "title", standardAnalyzer); Query query = parse.Parse("死神"); //Collector两个子类:TopScoreDocCollector 按照默认评分排序收集结果集 TopScoreDocCollector results = TopScoreDocCollector.Create(start + pagesize, false); //TopFieldCollector 按照自定义排序收集结果集 //Sort sort= new Sort(new SortField[] { new SortField("id", SortField.INT, false), new SortField("price", SortField.INT, true) }); //TopFieldCollector results= TopFieldCollector.Create(sort,start + pagesize, false, false, false, false); searcher.Search(query, results);//执行搜索 TopDocs tds = results.TopDocs(start, pagesize);//开始的地方和取得的条数 可以用来分页 Console.WriteLine(tds.TotalHits); ScoreDoc[] sd = tds.ScoreDocs; for (int i = 0; i < sd.Length; i++) { Console.WriteLine(searcher.Doc(sd[i].Doc).Get("title")); }
删除文档:
删除文档不会马上执行,而是被放在了内存的缓冲区,最后lucene会通过周期性刷新文档结构来执行操作,
与加入文档一样,你必须调用write的Commit()或close()方法提交更改。
即便删除操作已完成,磁盘空间也不会被马上释放,lucene只是将改文档标记为已删除
ndexWriter.Optimize();//调用索引优化,它将强制Lucene在删除一个文档后重新合并索引段。将马上执行删除操作,清除内存缓冲区的数据
IndexWriter write = new IndexWriter(dirctory, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_30),false, IndexWriter.MaxFieldLength.UNLIMITED);
indexWriter.DeleteDocuments(new Term("id", "1"));//删除文档 //indexWriter.Optimize();//调用索引优化,它将强制Lucene在删除一个文档后重新合并索引段。将马上执行删除操作,清除内存缓冲区的数据 indexWriter.Commit();//提交修改 indexWriter.HasDeletions();//检索索引中是否包含被标记为已删除的文档 int maxnum= indexWriter.MaxDoc();//返回索引中被删除和未被删除文档总数 int num = indexWriter.NumDocs();//只返回索引中未被删除的文档数 indexWriter.Close();
修改文档
* 你如果只更新文档中的部分域(表中的字段),如标题发生改变而正文没有变化,这种修改lucene是不支持的
* lucene只能删除旧文档,然后向索引中添加新文档,这要求旧文档必须包含新文档的所有其他为改变域的数据
* UpdateDocument该方法是先删除Term变量的所有文档,然后添加新文档(内部调用了DeleteDocuments和AddDocument)
Document docc = new Document(); docc.Add(new Field("id", "1", Field.Store.YES, Field.Index.NOT_ANALYZED));//被改变的域 docc.Add(new Field("country", "china中国", Field.Store.YES, Field.Index.NOT_ANALYZED)); docc.Add(new Field("des", "wen ming gu guo ", Field.Store.YES, Field.Index.NOT_ANALYZED)); indexWriter.AddDocument(docc); //修改id名为id,且值为1的域 //UpdateDocument该方法是先删除Term变量的所有文档,然后添加新文档(内部调用了DeleteDocuments和AddDocument) indexWriter.UpdateDocument(new Term("id", "1"),docc); indexWriter.Close();
一般在执行增量修改索引时:首先取出单位时间内所有被更新的商品,循环这些商品并且查询索引里是否包含该商品,包含就修改,不包含就新增。判断商品状态如果被设置为无效就从索引里移除
kucene的评分和排序:
lucene默认按照评分降序排列,评分是指相关性得分,匹配度越高得分越高。例如两个相或关系的条件,两个条件的都符合的文档得分要高于只符合其中一个条件的文档
索引查看工具:lukeall-4.0.0-ALPHA