lucene3.0中BooleanQuery 实现与或的复合搜索 .
BooleanClause用于表示布尔查询子句关系的类,
包
括:
BooleanClause.Occur.MUST,
BooleanClause.Occur.MUST_NOT,
BooleanClause.Occur.SHOULD。
必须包含,不能包含,可以包含三种.有以下6种组合:
1.MUST和MUST:取得连个查询子句的交集。
2.MUST和MUST_NOT:表示查询结果中不能包含MUST_NOT所对应得查询子句的检索结果。
3.SHOULD与MUST_NOT:连用时,功能同MUST和MUST_NOT。
4.SHOULD与MUST连用时,结果为MUST子句的检索结果,但是SHOULD可影响排序。
5.SHOULD与SHOULD:表示“或”关系,最终检索结果为所有检索子句的并集。
6.MUST_NOT和MUST_NOT:无意义,检索无结果。
...
构造出“与”的关系: bquery.Add(query, BooleanClause.Occur.MUST);
构造“或”关系:bquery.Add(query, BooleanClause.Occur.SHOULD);
构造“非”关系:bquery.Add(query, BooleanClause.Occur.MUST_NOT);
...
Field.Store.YES:存储字段值(未分词前的字段值)
Field.Store.NO:不存储,存储与索引没有关系
Field.Store.COMPRESS:压缩存储,用于长文本或二进制,但性能受损
Field.Index.ANALYZED:分词建索引
Field.Index.ANALYZED_NO_NORMS:分词建索引,但是Field的值不像通常那样被保存,而是只取一个byte,这样节约存储空间
Field.Index.NOT_ANALYZED:不分词且索引
Field.Index.NOT_ANALYZED_NO_NORMS:不分词建索引,Field的值去一个byte保存
TermVector表示文档的条目(由一个Document和Field定位)和它们在当前文档中所出现的次数
Field.TermVector.YES:为每个文档(Document)存储该字段的TermVector
Field.TermVector.NO:不存储TermVector
Field.TermVector.WITH_POSITIONS:存储位置
Field.TermVector.WITH_OFFSETS:存储偏移量
Field.TermVector.WITH_POSITIONS_OFFSETS:存储位置和偏移量
...
Apache Lucene 几种分词系统
1、 StopAnalyzer
StopAnalyzer能过滤词汇中的特定字符串和词汇,并且完成大写转小写的功能。
2、 StandardAnalyzer
StandardAnalyzer根据空格和符号来完成分词,还可以完成数字、字母、E-mail地址、IP地址以及中文字符的分析处理,还可以支持过滤词表,用来代替StopAnalyzer能够实现的过滤功能。
3、 SimpleAnalyzer
SimpleAnalyzer具备基本西文字符词汇分析的分词器,处理词汇单元时,以非字母字符作为分割符号。分词器不能做词汇的过滤,之进行词汇的分析和分割。输出地词汇单元完成小写字符转换,去掉标点符号等分割符。
在全文检索系统开发中,通常用来支持西文符号的处理,不支持中文。由于不完成单词过滤功能,所以不需要过滤词库支持。词汇分割策略上简单,使用非英文字符作为分割符,不需要分词词库的支持。
4、 WhitespaceAnalyzer
WhitespaceAnalyzer使用空格作为间隔符的词汇分割分词器。处理词汇单元的时候,以空格字符作为分割符号。分词器不做词汇过滤,也不进行小写字符转换。
实际中可以用来支持特定环境下的西文符号的处理。由于不完成单词过滤和小写字符转换功能,也不需要过滤词库支持。词汇分割策略上简单使用非英文字符作为分割符,不需要分词词库支持。
5、 KeywordAnalyzer
KeywordAnalyzer把整个输入作为一个单独词汇单元,方便特殊类型的文本进行索引和检索。针对邮政编码,地址等文本信息使用关键词分词器进行索引项建立非常方便。
6、 CJKAnalyzer
CJKAnalyzer内部调用CJKTokenizer分词器,对中文进行分词,同时使用StopFilter过滤器完成过滤功能,可以实现中文的多元切分和停用词过滤。在Lucene3.0版本中已经弃用。
7、 ChineseAnalyzer
ChineseAnalyzer功能与StandardAnalyzer分析器在处理中文是基本一致,都是切分成单个的双字节中文字符。在Lucene3.0版本中已经弃用。
8、 PerFieldAnalyzerWrapper
PerFieldAnalyzerWrapper功能主要用在针对不同的Field采用不同的Analyzer的场合。比如对于文件名,需要使用KeywordAnalyzer,而对于文件内容只使用StandardAnalyzer就可以了。通过addAnalyzer()可以添加分类器。
9、 IKAnalyzer
实现了以词典为基础的正反向全切分,以及正反向最大匹配切分两种方法。IKAnalyzer是第三方实现的分词器,继承自Lucene的Analyzer类,针对中文文本进行处理。
10、JE-Analysis
JE-Analysis是Lucene的中文分词组件,需要下载。
11、 ICTCLAS4J
ictclas4j中文分词系统是sinboy在中科院张华平和刘群老师的研制的FreeICTCLAS的基础上完成的一个java开源分词项目,简化了原分词程序的复杂度,旨在为广大的中文分词爱好者一个更好的学习机会。
12、 Imdict-Chinese-Analyzer
imdict-chinese-analyzer 是 imdict智能词典 的智能中文分词模块,算法基于隐马尔科夫模型(Hidden Markov Model, HMM),是中国科学院计算技术研究所的ictclas中文分词程序的重新实现(基于Java),可以直接为lucene搜索引擎提供简体中文分词支持。
13、 Paoding Analysis
Paoding Analysis中文分词具有极 高效率 和 高扩展性。引入隐喻,采用完全的面向对象设计,构思先进。其效率比较高,在PIII 1G内存个人机器上,1秒可准确分词100万汉字。采用基于不限制个数的词典文件对文章进行有效切分,使能够将对词汇分类定义。能够对未知的词汇进行合理解析。
14、 MMSeg4J
mmseg4j 用 Chih-Hao Tsai 的 MMSeg 算法(http://technology.chtsai.org/mmseg/ )实现的中文分词器,并实现 lucene 的 analyzer 和 solr 的TokenizerFactory 以方便在Lucene和Solr中使用。 MMSeg 算法有两种分词方法:Simple和Complex,都是基于正向最大匹配。Complex 加了四个规则过虑。官方说:词语的正确识别率达到了 98.41%。mmseg4j 已经实现了这两种分词算法。
...
using System; using System.Collections.Generic; using System.Web.Mvc; using Lucene.Net.Store; using Lucene.Net.Analysis; using Lucene.Net.Analysis.Standard; using Lucene.Net.Index; using Lucene.Net.Documents; using Lucene.Net.Search; using Lucene.Net.QueryParsers; using System.Diagnostics; namespace LuceneNet.Web.Controllers { public class HomeController : Controller { public ActionResult Index() { ViewBag.Message = "欢迎使用 ASP.NET MVC!"; return View(); } public ActionResult About() { return View(); } /// <summary> /// 添加索引 /// </summary> /// <returns></returns> public ActionResult AddIndex() { //为索引存储目录 string INDEX_STORE_PATH = Server.MapPath("~/SearchIndex"); #if DEBUG ///如果存在文件则删除(测试用) if (System.IO.Directory.Exists(INDEX_STORE_PATH)) { System.IO.Directory.Delete(INDEX_STORE_PATH, true); } #endif Directory indexDirectory = FSDirectory.Open(new System.IO.DirectoryInfo(INDEX_STORE_PATH)); Analyzer analyzer = new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29); IndexWriter writer = null; try { //检查索引文件是否存在 bool iscreate = !Lucene.Net.Index.IndexReader.IndexExists(indexDirectory); //如果索引文件不存在则创建索引文件,否则创建索引文件 writer = new IndexWriter(indexDirectory, analyzer, iscreate, IndexWriter.MaxFieldLength.UNLIMITED); //开始添加索引 foreach (var item in Get()) { Document doc = new Document(); doc.Add(new Field("id", item.Id, Field.Store.YES, Field.Index.ANALYZED));//存储,不分词索引 doc.Add(new Field("classid", item.ClassId, Field.Store.YES, Field.Index.NOT_ANALYZED));//存储,不分词索引 doc.Add(new Field("classname", item.ClassName, Field.Store.YES, Field.Index.ANALYZED));//存储,不分词索引 doc.Add(new Field("title", item.Title, Field.Store.YES, Field.Index.ANALYZED));//存储,分词索引 doc.Add(new Field("summary", item.Summary, Field.Store.YES, Field.Index.ANALYZED));//存储,分词索引 doc.Add(new Field("createtime", item.CreateTime.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED));//存储,分词索引 writer.AddDocument(doc); } writer.Optimize(); } catch (Exception) { throw; } finally { if (analyzer != null) analyzer.Close(); if (writer != null) writer.Close(); if (indexDirectory != null) indexDirectory.Close(); } return RedirectToAction("index"); } /// <summary> /// 搜索 /// </summary> /// <param name="k"></param> /// <param name="cid"></param> /// <returns></returns> public ActionResult Search(string k, string cid) { Stopwatch st = new Stopwatch(); st.Start();//计时开始 //为索引存储目录 string INDEX_STORE_PATH = Server.MapPath("~/SearchIndex"); var ver = Lucene.Net.Util.Version.LUCENE_29; Directory indexDirectory = FSDirectory.Open(new System.IO.DirectoryInfo(INDEX_STORE_PATH)); Analyzer analyzer = new StandardAnalyzer(ver); IndexSearcher searcher = null; List<Article> list; int recCount = 0; try { searcher = new IndexSearcher(indexDirectory, true); string[] fields = { "title", "summary" }; BooleanQuery booleanQuery = new BooleanQuery(); //多字段查询同时搜索title和summary MultiFieldQueryParser parser = new MultiFieldQueryParser(ver, fields, analyzer); Query query = parser.Parse(k); //Query query1 = new QueryParser(ver, "classid", analyzer).Parse("1"); //TermQuery只能查询不分词的索引(Field.Index.NOT_ANALYZED) Query query1 = new TermQuery(new Term("id", "1")); //当classname为ANALYZED时搜不到 // Query query2 = new TermQuery(new Term("classname", "体育新闻")); //只有当classname为NOT_ANALYZED才可以搜得到, //由此得出TermQuery只能查询不分词的索引(Field.Index.NOT_ANALYZED)的结论 //但当id为ANALYZED时TermQuery却可以收的到, //当搜classname中包含“体”时即Query query2 = new TermQuery(new Term("classname", "体")); //当搜classname中包含“育”时即Query query2 = new TermQuery(new Term("classname", "育")); //可以搜得到。因此,由此得出,TermQuery搜的是最小单位,由此又得出Lucene是把“体育新闻”拆分成了"体/育/新/闻"四部分 //听说Lucene分词是按空格分的,那么把“体育新闻”,改成“体育 新闻”后再重新生成索引是不是可以搜的到呢? //Query query2 = new TermQuery(new Term("classname", "体育")); //但是结果却是搜不到,纳闷...难道Lucene的分词不是这么分而是更复杂? //StandardAnalyzer看来是对中文分词不怎么好,当ClassName = "sports news"可以搜sports和news //StandardAnalyzer只支持英文的空格分词 Query query2 = new TermQuery(new Term("classname", k)); //关于QueryParser的搜索当k为Empty或null时会报错注意处理 //Query query3 = new QueryParser(ver, "title", analyzer).Parse(k); Query query3 = new QueryParser(ver, "title", analyzer).Parse(k); Query query4 = new PrefixQuery(new Term("classname", k)); Query query5 = new QueryParser(ver, "title", analyzer).Parse(k); TermRangeQuery query6 = new TermRangeQuery("createtime", "2012-1-3", "2012-5-3", true, true); //booleanQuery.Add(query1, BooleanClause.Occur.MUST); //booleanQuery.Add(query2, BooleanClause.Occur.MUST); //booleanQuery.Add(query3, BooleanClause.Occur.MUST); booleanQuery.Add(query4, BooleanClause.Occur.MUST); //booleanQuery.Add(query5, BooleanClause.Occur.MUST); booleanQuery.Add(query6, BooleanClause.Occur.MUST); TopDocs ts = searcher.Search(booleanQuery, null, 100);//执行搜索,获取查询结果集对象 recCount = ts.totalHits;//获取命中的文档个数 ScoreDoc[] hits = ts.scoreDocs;//获取命中的文档信息对象 st.Stop();//计时停止 ViewBag.EvenTime = string.Format("{0}毫秒,生成的Query语句:{1}", st.ElapsedMilliseconds, booleanQuery.ToString()); ViewBag.Count = recCount; list = new List<Article>(); foreach (var item in hits) { list.Add(new Article() { Id = searcher.Doc(item.doc).Get("id"), ClassId = searcher.Doc(item.doc).Get("classid"), ClassName = searcher.Doc(item.doc).Get("classname"), Title = searcher.Doc(item.doc).Get("title"), Summary = searcher.Doc(item.doc).Get("summary"), Score = item.score.ToString(), CreateTime = DateTime.Parse(searcher.Doc(item.doc).Get("createtime")) }); } } catch (Exception) { throw; } finally { if (searcher != null) { searcher.Close(); } } return View(list); } public List<Article> Get() { List<Article> list = new List<Article>(); list.Add(new Article() { Id = "1", ClassId = "1", ClassName = "体育新闻", Title = "微软发布MVC4.0了", Summary = "微软发布MVC4.0了,此版本更加强大", CreateTime = DateTime.Parse("2012-2-3") }); list.Add(new Article() { Id = "2", ClassId = "1", ClassName = "IT新闻", Title = "跟谷歌测试工程师的对话", Summary = "本文主人公Alan是谷歌的一名的软件测试工程师,他的工作对象是谷歌的DoubleClick广告管理系统(Bid Manager),这个系统提供让广告代理商和广告客户在多个广告上进行报价竞标的功能。", CreateTime = DateTime.Parse("2012-3-3") }); list.Add(new Article() { Id = "3", ClassId = "1", ClassName = "体育 新闻", Title = "好的程序员应该熟悉的几门编程语言", Summary = "如果想成为一个好的程序员,甚至架构师、技术总监等,显然只精通一种编程语言是不够的,还应该在常见领域学会几门编程语言,正如我们要成为高级人才不仅要会中文还要会英文", CreateTime = DateTime.Parse("2012-4-3") }); list.Add(new Article() { Id = "4", ClassId = "2", ClassName = "娱乐新闻", Title = "Javascript开发《三国志曹操传》-开源讲座(五)-可移动地图的实现", Summary = "这一讲的内容很简单,大家理解起来会更快。因此我只对重点加以分析,其他的就轮到大家思考哦!首先来说,我对游戏开发可以算是不怎么深入,因为现在的程序员爱用canvas,我却就只会拿几个div凑和。", CreateTime = DateTime.Parse("2012-5-3") }); list.Add(new Article() { Id = "5", ClassId = "2", ClassName = "体育新闻", Title = "Android之BaseExpandableListAdapter使用心得", Summary = " 但是我最近做那个QQ项目是遇到一个问题,如果给这个ExpandableListView添加动态从网上获取的数据呢?前面跟大家分享的时候,是用了静态的数据,很好处理。", CreateTime = DateTime.Parse("2012-6-3") }); list.Add(new Article() { Id = "6", ClassId = "3", ClassName = "sports news", Title = "对话CSDN蒋涛:微软移动互联网马太效应不可避免,小团队需学会利用平台", Summary = "CSDN是全球最大的中文IT社区,也是雷锋网最重要的合作伙伴之一,自1999年创办至今,有着非常强大的业界影响力和号召力,其专注IT信息传播、技术交流、教育培训和专业技术人才服务,在2012年移动开发者大会即将举办之际,雷锋网对CSDN的掌门人蒋涛做了一次专访,一起探讨移动互联网的新技术浪潮和下一波发展趋势。", CreateTime = DateTime.Parse("2012-7-3") }); list.Add(new Article() { Id = "7", ClassId = "3", ClassName = "体育新闻", Title = "基于MySQL的分布式事务控制方案", Summary = "基于MySQL的分布式事务控制方案", CreateTime = DateTime.Parse("2012-8-3") }); list.Add(new Article() { Id = "8", ClassId = "4", ClassName = "sports news", Title = "IOS和Android开发的一些个人感受", Summary = "最近公司的产品 Android版本第二版也算到了收尾,新加了几个功能性模块,我基本也就捡了几个好玩的模块做了下。", CreateTime = DateTime.Parse("2012-9-3") }); list.Add(new Article() { Id = "9", ClassId = "5", ClassName = "IT资讯", Title = "Google Code的简单使用", Summary = "google code简介:用于管理代码的仓库,反正我是这么理解的。就比我们在公司的时候也会有个用于存放公司代码的主机一样,google同样给我们提供了这样的一个host。这样我们可以在不同电脑不同地方随时的checkout,commit,同时分享我们的项目。", CreateTime = DateTime.Parse("2012-10-3") }); list.Add(new Article() { Id = "10", ClassId = "33", ClassName = "IT资讯", Title = "谷歌在印度推Gmail免费短信服务", Summary = "歌一直在努力桥接发展中国家功能手机SMS服务和Gmail之间的服务,这不,近日谷歌在印度推出“Gmail SMS”服务,这使得印度的Gmail用户可以从Gmail的窗口发送信息到手机上并且接受聊天信息的回复,目前谷歌的这项服务已经得到印度的八大运营商的支持。", CreateTime = DateTime.Parse("2012-11-3") }); list.Add(new Article() { Id = "11", ClassId = "11", ClassName = "体育新闻", Title = "鲍尔默:微软新时代 软硬结合“赢”未来", Summary = "微软CEO鲍尔默在年度公开信中表示,微软在未来将紧密结合硬件和软件。鲍尔默认为,这是微软的一个新时代。“我们看到了前所未有的机会,我们对此很兴奋,并且保持着乐观的心态。”", CreateTime = DateTime.Parse("2012-12-3") }); return list; } } public class Article { public string Id { get; set; } public string ClassId { get; set; } public string ClassName { get; set; } public string Title { get; set; } public string Summary { get; set; } public string Score { get; set; } public DateTime CreateTime { get; set; } } }
...
...