The goal of Apache Lucene and Solr is to provide world class search capabilities
上面这句话是apache在官网上对Lucene的介绍。本文对Lucene做一个入门级的介绍,其实每一部分都可以单独写一篇文章。
当数据量越来越大的时候,大数据量对于数据库还是有一定压力,一般采用分表,分表后读取数据也可能会比较慢,尤其是有很多搜索条件。如果数据量不大,并且读取写入频繁,可以采用Radis,实现master-slave(主从)同步。如果千万级别的数据量且对数据的读取速度有一定要求,就需要建索引,Lucene是不错的选择。
Lucene通过对存储内容建立索引,提高查询效率,索引文件存储在硬盘/内存上,索引类似字典上面的a-b-c-d顺序,a-b-c-d对应Lucene中的Field。
Lucene常用类介绍
- **(1)IndexWriter : **
An IndexWriter creates and maintains an index. 用来创建并管理索引文件。该类负责创建新索引或者打开已有索引,以及向索引中添加、删除或更新索引文档的信息,但不能用于读取或搜索索引。IndexWriter需要开辟一定空间来存储索引,该功能可以由Directory完成。
IndexWriter有以下几种创建模式:
IndexWriterConfig.OpenMode
APPEND
Opens an existing index.
CREATE
Creates a new index or overwrites an existing one.
CREATE_OR_APPEND
Creates a new index if one does not exist, otherwise it opens the index and documents will be appended.
另外有几点需要注意:
1:如果在创建索引文件过程中出现OutOfMemoryError,随之调用commit()会出现IllegalStateException,Lucene内部会记录当前出现异常的位置,此时必须调用close()方法处理异常,close()方法内部会回滚状态到上一次提交的位置。当然也可以手动调用rollback()。所以在try,catch后一定加上finally,并在finally中调用close()方法。
2:Lucene方法都是线程安全的,内部涉及写入文件都是加锁的,可以多线程使用,所以在使用时不需要再加锁,尤其是IndexWriter实例不要加锁,不然有可能造成死锁。
(2)Directory :A Directory is a flat list of files. Files may be written once, when they are created. Once a file is created it may only be opened for read, or deleted. Random access is permitted both when reading and writing. 索引存放的位置,它是一个抽象类,它的子类负责具体制定索引的存储路径。支持读共享,写独占的方式来访问索引目录。lucene提供了两种索引存放的位置,一种是磁盘,一种是内存。一般情况将索引放在磁盘上;相应地lucene提供了FSDirectory(硬盘)和RAMDirectory(内存)两个类。Java的io操作Api都是通过这个类来操作。
(3)Analyzer TokenStream Token: 这三个都是analysis包下面的类,这里放在一起说了。
Package org.apache.lucene.analysis
API and code to convert text into indexable/searchable tokens. Covers Analyzer and related classes.
Parsing? Tokenization? Analysis!
Lucene, an indexing and search library, accepts only plain text input.所以analysis包就是用来处理文本的。
Analyzer :An Analyzer builds TokenStreams, which analyze text. It thus represents a policy for extracting index terms from text. 分析器或分词器,主要用于分析搜索引擎遇到的各种文本,Analyzer的工作是一个复杂的过程:把一个字符串按某种规则划分成一个个词语,并去除其中的无效词语,去掉有利于缩小索引文件、提高效率、提高命中率。分词的规则千变万化,在analysis包中含有多种分词器,当然也可以自己定义分词器。Lucene文档给出了以下几种实现好的分词器:
- Common: Analyzers for indexing content in different languages and domains. 这里面有很多,常用的有:SimpleAnalyzer、CJKAnalyzer、IKAnalyzer、StandardAnalyzer。
- ICU: Exposes functionality from ICU to Apache Lucene.
- Kuromoji: Morphological analyzer for Japanese text.
- Morfologik: Dictionary-driven lemmatization for the Polish language.
- Phonetic: Analysis for indexing phonetic signatures (for sounds-alike search).
- Smart Chinese: Analyzer for Simplified Chinese, which indexes words.
- Stempel: Algorithmic Stemmer for the Polish Language.
- UIMA: Analysis integration with Apache UIMA.
TokenStream 是用来代替Token的Api:从Lucene2.9以后不再推荐使用Token;
TokenStream 介绍:A TokenStream enumerates the sequence of tokens, either from Field Document or from query text. 分词器做好处理之后得到的一个流,这个流中存储了分词的各种信息.可以通过TokenStream有效的获取到分词单元,从而获取各个分词的信息。
//第一个参数只是标识性没有实际作用
TokenStream stream = analyzer.tokenStream("", new StringReader(str));
//获取词与词之间的位置增量
PositionIncrementAttribute postiona = stream.addAttribute(PositionIncrementAttribute.class);
//获取各个单词之间的偏移量
OffsetAttribute offseta = stream.addAttribute(OffsetAttribute.class);
//获取每个单词信息
CharTermAttribute chara = stream.addAttribute(CharTermAttribute.class);
//获取当前分词的类型
TypeAttribute typea = stream.addAttribute(TypeAttribute.class);
while(stream.incrementToken()){
System.out.print("位置增量" +postiona.getPositionIncrement()+":\t");
System.out.println(chara+"\t[" + offseta.startOffset()+" - " + offseta.endOffset() + "]\t<" + typea +">");
}
System.out.println();
- ** (4) Document : **The logical representation of a Document for indexing and searching.文档 Document相当于一个要进行索引的单元,可以是文本文件、字符串或者数据库表的一条记录等等,一条记录经过索引之后,就是以一个Document的形式存储在索引文件,索引的文件都必须转化为Document对象才能进行索引。这个没什么可说的,下面会有一段代码简单描述索引创建过程看一下就知道了。
- (5) Field : 一个Document可以包含多个信息域,比如一篇文章可以包含“标题”、“正文”等信息域,这些信息域就是通过Field在Document中存储的。一个Field包含内容,是否存储,是否分词等,这可由构造函数看出:
xxmcField = new Field("xxmc", "", Field.Store.YES, Field.Index.ANALYZED_NO_NORMS);
xxmc.setValue("laotie");
其中"xxmc"是键值中的键名,"laotie"是值,Field.Store.YES代表存储内容,如果使用索引查询后需要展示其内容,那么选择存储即可,如果不需要展示只是用来索引,那么Field.Store.NO,分词则表示是否用分词器对内容分词,便于搜索。
- **(6) IndexReader : **
IndexReader is an abstract class, providing an interface for accessing an index. 打开一个Directory读取索引类。同样值得注意的是,Lucene在内部已经保证了线程安全,不需外部对IndexReader的实例加锁,不然可能造成死锁。
(7) IndexSearcher:To perform a search, applications usually call IndexSearcher.search(Query,int) or IndexSearcher.search(Query,Filter,int). 是lucene中最基本的检索工具,所有的检索都会用到IndexSearcher工具。创建索引最终就是为了搜索,这部分涉及比较复杂的就是打分,也就是一个索引的分数即其重要程度。而其分值可以直接影响搜索结果的排名。关于评分机制可以参考。
(8)Query 查询,抽象类,必须通过一系列子类来表述检索的具体需求,lucene中支持模糊查询,语义查询,短语查询,组合查询等等,如有 TermQuery,BooleanQuery,RangeQuery,WildcardQuery等一些类。
创建索引的典型方式,重用Document与Field:
Document doc = new Document();
Field field1 = new TextField("field1", field1Value, Field.Store.YES);
doc.add(field1);
Field field2 = new StringField("field2", field2Value,Field.Store.YES);
doc.add(field2);
while ((line = br.readLine()) != null) {
field1.setStringValue("field1Value");
field2.setStringValue("field2Value");
writer.addDocument(doc);
}
- 重点介绍一下这几种Query方式
TermQuery
这是一种最简单的查询,类似键值对,使用键和值去索引里查询包含对应键值的数据。可以用来构成BooleanQuery.
BooleanQuery
A Query that matches documents matching boolean combinations of other queries
其实这个就是一种组合查询,其中组合方式可以选择MUST, NOT_MUST, SHOULD。其中SHOULD其实只是按打分影响排名,具体组合参考:
- 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:无意义,检索无结果。
RangeQuery:NumericRangeQuery和TermRangeQuery两种
范围查询,一般NumericRangeQuery用的比较多,用来查询数字范围,比如价格,人数等等。TermRangeQuery也是范围查询但最终转为AscII码,文档上写的就是最终比较byte,Byte.compareTo(byte). 所以不如NumericRangeQuery查询使用的多。
WildcardQuery
这个是通配符查询,使用文档里的通配符查询每个item中的内容,比如"use*"可以查到"useful"、"user"。注意:这个查询比较慢,因为要查询每个item,,为了避免极慢的查询速度,请不要使用以星号开头的通配符进行查询。另外,WildcardQuery对于用户输入的查询关键字是大小写敏感的,请不要使用大写形式,因为索引中的Term都是小写形式的。
下一篇介绍:Lucene 建立及读取的例子,优化的几个方面