lucene是一个很容易上手,纯java语言的全文索引检索工具包。
Lucene的作者是资深的全文索引/检索专家,最开始发布在他本人的主页上,2001年10月贡献给APACHE,成为APACHE基金jakarta的一个子项目。
目前,lucene广泛用于全文索引/检索的项目中。
Lucene 原理
lucene的检索算法属于索引检索,即用空间来换取时间,对需要检索的文件、字符流进行全文索引,在检索的时候对索引进行快速的检索,得到检索位置,这个位置记录检索词出现的文件路径或者某个关键词。
在使用数据库的项目中,不使用数据库进行检索的原因主要是:数据库在非精确查询的时候使用查询语言“like %keyword%”,对数据库进行查询是对所有记录遍历,并对字段进行“%keyword%”匹配,在数据库的数据庞大以及某个字段存储的数据量庞大的时候,这种遍历是致命的,它需要对所有的记录进行匹配查询。因此,lucene主要适用于文档集的全文检索,以及海量数据库的模糊检索,特别是对数据库的 xml或者大数据的字符类型。
全文检索的实现机制
Lucene的API接口设计的比较通用,输入输出结构都很像数据库的表==>记录==>字段,所以很多传统的应用的文件、数据库等都可以比较方便的映射到Lucene的存储结构/接口中。总体上看:可以先把Lucene当成一个支持全文索引的数据库系统。
比较一下Lucene和数据库:
全文检索 ≠ like "%keyword%"
-------------------------------------------------------------------------
Lucene作为一个全文检索引擎,其具有如下突出的优点:
(1)索引文件格式独立于应用平台。Lucene定义了一套以8位字节为基础的索引文件格式,使得兼容系统或者不同平台的应用能够共享建立的索引文件。
(2)在传统全文检索引擎的倒排索引的基础上,实现了分块索引,能够针对新的文件建立小文件索引,提升索引速度。然后通过与原有索引的合并,达到优化的目的。
(3)优秀的面向对象的系统架构,使得对于Lucene扩展的学习难度降低,方便扩充新功能。
(4)设计了独立于语言和文件格式的文本分析接口,索引器通过接受Token流完成索引文件的创立,用户扩展新的语言和文件格式,只需要实现文本分析的接口。
(5)已经默认实现了一套强大的查询引擎,用户无需自己编写代码即使系统可获得强大的查询能力,Lucene的查询实现中默认实现了布尔操作、模糊查询(Fuzzy Search[11])、分组查询等等。
为了对文档进行索引,Lucene 提供了五个基础的类,他们分别是 Document, Field, IndexWriter, Analyzer, Directory。下面我们分别介绍一下这五个类的用途:
Document
Document 是用来描述文档的,这里的文档可以指一个 HTML 页面,一封电子邮件,或者是一个文本文件。一个 Document 对象由多个 Field 对象组成的。可以把一个 Document 对象想象成数据库中的一个记录,而每个 Field 对象就是记录的一个字段。
Field
Field 对象是用来描述一个文档的某个属性的,比如一封电子邮件的标题和内容可以用两个 Field 对象分别描述。
Analyzer
在一个文档被索引之前,首先需要对文档内容进行分词处理,这部分工作就是由 Analyzer 来做的。Analyzer 类是一个抽象类,它有多个实现。针对不同的语言和应用需要选择适合的 Analyzer。Analyzer 把分词后的内容交给 IndexWriter 来建立索引。
IndexWriter
IndexWriter 是 Lucene 用来创建索引的一个核心的类,他的作用是把一个个的 Document 对象加到索引中来。
Directory
这个类代表了 Lucene 的索引的存储的位置,这是一个抽象类,它目前有两个实现,第一个是 FSDirectory,它表示一个存储在文件系统中的索引的位置。第二个是 RAMDirectory,它表示一个存储在内存当中的索引的位置。
利用Lucene进行搜索就像建立索引一样也是非常方便的。在上面一部分中,我们已经为一个目录下的文本文档建立好了索引,现在我们就要在这个索引 上进行搜索以找到包含某个关键词或短语的文档。Lucene提供了几个基础的类来完成这个过程,它们分别是呢IndexSearcher, Term, Query, TermQuery, Hits. 下面我们分别介绍这几个类的功能。
Query
这是一个抽象类,他有多个实现,比如TermQuery, BooleanQuery, PrefixQuery. 这个类的目的是把用户输入的查询字符串封装成Lucene能够识别的Query。
Term
Term是搜索的基本单位,一个Term对象有两个String类型的域组成。生成一个Term对象可以有如下一条语句来完成:Term term = new Term(“fieldName”,”queryWord”); 其中第一个参数代表了要在文档的哪一个Field上进行查找,第二个参数代表了要查询的关键词。
TermQuery
TermQuery是抽象类Query的一个子类,它同时也是Lucene支持的最为基本的一个查询类。生成一个TermQuery对象由如下语句完成: TermQuery termQuery = new TermQuery(new Term(“fieldName”,”queryWord”)); 它的构造函数只接受一个参数,那就是一个Term对象。
IndexSearcher
IndexSearcher是用来在建立好的索引上进行搜索的。它只能以只读的方式打开一个索引,所以可以有多个IndexSearcher的实例在一个索引上进行操作。
Hits
Hits是用来保存搜索的结果的。
Lucene查询语法:
一、模糊查询
Term Modifiers
Lucene支持在Term中使用通配符来支持模糊查询。
Wildcard Searches [类:org.apache.lucene.search.WildcardQuery]
Lucene支持单个或者多个字符的通配符查询,匹配单一字符使用符号"?",匹配多个字符使用符号"*".
"?"通配符将查找所有满足通过一个字符替换后符合条件的文档。比如:搜索"test"和"text"你可以使用:
te?t
"*"通配符将查询0个或者多个字符替换后符合条件的。举例来说,查询test,tests或者tester,你可以使用一下字符串来搜索:
test*
当然,你也可以将"*"放在字符的中间
te*t
注意:你不能将"*"和"?"放在第一个字符来查询。(Lucene应该是出于性能考虑,所以不支持该功能)
Fuzzy Searches [org.apache.lucene.search.FuzzyQuery]
Lucene支持基于编辑距离算法的模糊搜索,你可以使用波浪符号"~"放在查询词的后面,比如搜索一个与"roam"拼写相近的词可以使用:
roam~
该查询将寻找类似"foam"和"roams"等的词语。也可以说是相似度查询。
Proximity Searches [org.apache.lucene.search.PrefixQuery]
Lucene支持指定距离查询,你可以使用波浪号"~"加数字在查询词后。举例来说搜索"apache"和"jakarta"距离10个字符以内,你可以使用如下语法:
"jakarta apache"~10
通过这个语法支持,我们可以单字索引,分词查询,分词完后,满足每个词的单字必须间距为1.这样可以保证100%的召回率,但是在索引方面将造成索引臃肿,同时查询速度也将在某程度上降低,一般来说,在150W文章数据到200W数据的时候性能将会明显的降低。
Range Searches [org.apache.lucene.search.RangeQuery]
范围查询允许你指定某个字段最大值和最小值,查询在二者之间的所有文档。范围查询可以包含或者不包含最大值和最小值,排序是按照字典顺序来排序的。
mod_date:[20020101 TO 20030101]
这个将查找满足mode_date字段在大于等于20020101,小于等于20030101范围的所有文档,注意:范围查询并不是为日期字段专设的,你也可以对非日期字段进行范围查询。
title:{Aida TO Carmen}
这个将查找所有标题在Aida和Carmen之间但不包含Aida和Carmen的文档。包含最大值和最小值的查询使用方括号,排除则使用花括号。
二、优先级
Boosting a Term
Lucene支持给不同的查询词设置不同的权重。设置权重使用"^"符号,将"^"放于查询词的尾部,同时跟上权重值,权重因子越大,该词越重要。设置权重允许你通过给不同的查询词设置不同的权重来影响文档的相关性,假如你在搜索:
jakarta apache
如果你认为"jakarta"在查询时中更加重要,你可以使用如下语法:
jakarta^4 apache
这将使含有Jakarta的文档具有更高的相关性,同样你也可以给短语设置权重如下:
"jakarta apache"^4 "jakarta lucene"
在默认情况下,权重因子为1,当然权重因子也可以小于1.
三、Term操作符
Boolean operators
布尔操作符可以将多个Term合并为一个复杂的逻辑查询。
Lucene支持AND,+,OR,NOT, -作为操作符号。注意,所有的符号必须为大写。
OR
OR操作符默认的连接操作符。这意味着,当没有给多个Term显式指定操作符时,将使用OR,只要其中一个Term含有,则可以查询出文档,这跟逻辑符 号||的意思相似。假设我们查询一个文档含有"jakarta apache"或者"jakarta"时,我们可以使用如下语法:
"jakarta apache" jakarta
或者
"jakarta apache" OR jakarta
AND
AND操作符规定必须所有的Term都出现才能满足查询条件,这跟逻辑符号&&意思相似。如果我们要搜索一个文档中同时含有"jakarta apache"和"jakarta lucene",我们可以使用如下语法:
"jakarta apache" AND "jakarta lucene"
+
+操作符规定在其后的Term必须出现在文档中,也就是查询词中的MUST属性。举个例子来说,当我们要查询一个文档必须包含"jakarta",同时可以包含也可以不包含"lucene"时,我们可以使用如下语法:
+jakarta apache
NOT
NOT操作符规定查询的文档必须不包含NOT之后的Term,这跟逻辑符号中的!相似。当我们要搜索一篇文档中必须含有"jakarta apache"同时不能含有"Jakarta lucene"时,我们可以使用如下查询;
"jakarta apache" NOT "jakarta lucene"
注意:NOT操作符不能使用在单独Term中,举例来说,以下查询将返回无结果:
NOT "jakarta apache"
-
-操作符排除了包含其后Term的文档,跟NOT有点类似,假设我们要搜索"Jakarta apache"但不包含"Jakarta lucene"时,我们使用如下语法:
"jakarta apache" -"jakarta lucene"
Grouping
Lucene支持使用圆括号来将查询表达式分组,这将在控制布尔控制查询中非常有用。举例来说:当搜索必须含有"website",另外必须含有"jakarta"和"apache"之一,我们可以用如下语法:
(jakarta OR apache) AND website
这种语法对消除歧义,确保查询表达式的正确性具有很大的意义。
Field Grouping
Lucene支持对字段用圆括号来进行分组,当我们要查询标题中含有"return"和"pink ranther"时,我们可以使用如下语法:
title:(+return +"pink panther")
Escaping Special Characters
Lucene支持转义查询中的特殊字符,以下是Lucene的特殊字符清单:
+ - && || ! ( ) { } [ ] ^ " ~ * ? : \
转义特殊字符我们可以使用符号"\"放于字符之前。比如我们要搜索(1+1):2,我们可以使用如下语法:
\(1\+1\)\:2
Tips: QueryParser.escape(q) 可转换q中含有查询关键字的字符!如:* ,? 等
附上源码实例:
package com.jiepu.lucene_23; import java.io.IOException; import java.util.BitSet; import java.util.Iterator; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.Term; import org.apache.lucene.index.TermDocs; import org.apache.lucene.queryParser.QueryParser; import org.apache.lucene.search.BooleanClause; import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.Filter; import org.apache.lucene.search.FilteredQuery; import org.apache.lucene.search.Hits; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.PhraseQuery; import org.apache.lucene.search.PrefixQuery; import org.apache.lucene.search.Query; import org.apache.lucene.search.RangeQuery; import org.apache.lucene.search.ScoreDoc; import org.apache.lucene.search.TopDocCollector; import org.apache.lucene.store.Directory; import org.apache.lucene.store.RAMDirectory; //http://my.oschina.net/lsw90/blog/186732 public class Example { public static void main(String[] args) throws Exception { testIndexAndSearchold(); } public static void testIndexAndSearchold() throws Exception { Analyzer analyzer = new StandardAnalyzer(); Directory directory = new RAMDirectory(); // Directory directory = FSDirectory.getDirectory("/tmp/testindex"); IndexWriter iwriter = new IndexWriter(directory, analyzer,true); Document doc = new Document(); //搜索引擎支持包含字符的检索,如用户输入“Add”,可检索出包含“Add”内容的词条;用户输入“dd”,也可检索出包含“Add”内容的词条。 doc.add(new Field("name", "text 麻痹的 addd dd ", Field.Store.YES,Field.Index.TOKENIZED)); doc.add(new Field("content", "我是内容1号 content text 麻痹的 addddd 你是猪 ", Field.Store.YES,Field.Index.TOKENIZED)); iwriter.addDocument(doc); Document doc2 = new Document(); String text = "This is the text add to be indexed. 你好啊 呵呵 内存索引"; doc2.add(new Field("name", text, Field.Store.YES,Field.Index.TOKENIZED)); doc2.add(new Field("content", "我是内容2号 content 麻痹的 addddd 你是猪 ", Field.Store.YES,Field.Index.TOKENIZED)); iwriter.addDocument(doc2); Document doc3 = new Document(); doc3.add(new Field("name", "add hello 测试的数据", Field.Store.YES,Field.Index.TOKENIZED)); doc3.add(new Field("content", "我是内容3号 content addddd 你是猪 ", Field.Store.YES,Field.Index.TOKENIZED)); iwriter.addDocument(doc3); iwriter.optimize(); iwriter.close(); IndexSearcher isearcher = new IndexSearcher(directory); //新增MultiFieldQueryParser等,修复bug //修改QueryParser支持?通配符 //新增CachingWrapperFilter和PerFieldAnalyzerWrapper等 //新增ParallelMultiSearcher等 //新增MMapDirectory等 //新增FieldSelector等 //新增BoostingTermQuery等 //新增SpanQueryFilter等 //新增QueryAutoStopWordAnalyzer等 //新增FieldCacheRangeFilter等 //新增AttributeFactory等 //新增ReusableAnalyzerBase等,新增FieldValueFilter等 //新增RegexpQuery等,新增BloomFilteringPostingsFormat等 //新增AnalyzingSuggester和FuzzySuggester等 //新增AutomatonQuery ,用来返回所有Term匹配有限状态机文章列表 //当前版本Lucene 4.1已经实现了所有这些主流的检索模型。支持TFIDF相似度度量,最佳匹配Okapi BM25相似度度量,随机分歧DFR相似度度量,Dirichlet和JelinekMercer相似度度量,IB相似度度量。 //通配符查询,继承自MultiTermQuery 支持通配符:* ? ~ 说明:* 匹配任何字符, ? 匹配单一字符 //WildcardQuery query = new WildcardQuery(new Term("name","*dd*")); //模糊查询,参数:项+匹配度,增加"~"后缀实现模糊查询 //FuzzyQuery query=new FuzzyQuery(new Term("name", "add"),0.9f); /* SpanQuery按照词在文章中的距离或者查询几个相邻词的查询 SpanQuery包括以下几种 SpanTermQuery:词距查询的基础,结果和TermQuery相似,只不过是增加了查询结果中单词的距离信息。 SpanFirstQuery:在指定距离可以找到第一个单词的查询。 SpanNearQuery:查询的几个语句之间保持者一定的距离。 SpanOrQuery:同时查询几个词句查询。 SpanNotQuery:从一个词距查询结果中,去除一个词距查询。*/ //跨度查询,范围查询 //SpanTermQuery query=new SpanTermQuery(new Term("name", "add*")); //权重值查询? //BoostingTermQuery query=new BoostingTermQuery(new Term("name", "dd")); //项匹配查询=精确查询 //TermQuery query=new TermQuery(new Term("name", "add")); //多字段查询 //QueryParser parser=new MultiFieldQueryParser(new String[]{"content","name"}, analyzer); //Query query = parser.parse("add"); //QueryParser parser = new QueryParser("name",analyzer); //Query query = parser.parse("add"); //词组查询 //PhraseQuery phraseQuery=new PhraseQuery(); //phraseQuery.add(new Term("name", "hello")); //多短语查询? //MultiPhraseQuery query=new MultiPhraseQuery(); //query.add(new Term("name", "hello")); //二层过滤包装 //FilteredQuery filteredQuery=new FilteredQuery(query,); //RangeQuery query=new RangeQuery(new Term("name", "dd"), new Term("name", "add"), false); //多条件 结合查询 /* BooleanQuery booleanQuery=new BooleanQuery(); //BooleanClause clause=new BooleanClause(); booleanQuery.add(phraseQuery, BooleanClause.Occur.MUST); booleanQuery.add(query,BooleanClause.Occur.MUST); System.out.println(booleanQuery.toString()); */ //前缀搜索,相当于add* PrefixQuery query=new PrefixQuery(new Term("name", "dd")); System.out.println(query.toString()); TopDocCollector hits = new TopDocCollector(100); isearcher.search(query, hits); //Hits hits2=isearcher.search(query); //hits2.doc(0).get("content"); System.out.println("TotalHits:"+hits.getTotalHits()); System.out.println("结果:"); ScoreDoc[] scoreDocs=hits.topDocs().scoreDocs;//该方法会影响hits.topDocs().scoreDocs[i].length for (int i = 0; i < scoreDocs.length; i++) { int doc_id=scoreDocs[i].doc; System.out.println("命中的文档编号id="+doc_id); Document hitDoc = isearcher.doc(doc_id); System.out.println("name="+hitDoc.get("name")+"-content="+hitDoc.get("content")); } isearcher.close(); directory.close(); } }