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包含一些公共工具类,例如时间和字符串之间的转换工具。