lucene学习笔记
- 格式:pdf
- 大小:195.10 KB
- 文档页数:10
lucene笔记-视频配套笔记传智播客 Java学院传智.入云龙全文检索技术之Lucene1 课程计划1、什么全文检索数据类型:结构化数据和非结构化数据非结构化数据查询方法全文检索的概念2、什么是Lucene3、Lucene实现全文检索的流程(重点)4、Lucene的入门程序(重点)5、分析器Analyzer(重点)7、索引库的维护(重点)i. 索引库的增加索引ii. 删除索引iii. 修改索引8、索引库的查询(重点)i. 使用Query的子类查询ii. 使用QueryParser查询8、相关的排序2 什么全文检索结构化数据:数据的长度类型是固定的。
数据库中的数据非结构化数据:长度不固定格式不固定。
Word文档、excel、pdf、txt。
2.1 结构化数据的查询使用sql语句查询就可以2.2 非结构化数据的查询需求:在文件中找出包含java字符的文档。
传智播客 Java学院传智.入云龙2.2.1 实现方法1、目测2、顺序扫描。
如果文件量大的话比如说20G,顺序扫描会变的很慢。
3、非结构化的数据变结构化先从文件中找出文件所有的词汇,形成一个列表。
查询的时候直接查询列表,找到关键词后根据这词找到相应的文档。
一次创建多次使用。
2.3 全文检索的概念这种先建立索引,再对索引进行搜索的过程就叫全文检索(Full-text Search)。
2.4 全文检索应用领域1、搜索引擎,百度、谷歌2、站内搜索a) 微博搜索b) 论坛搜索3、电商搜索,搜索的是商品信息。
3 什么是Lucene3.1 Lucene的概念Lucene是apache下的一个开放源代码的全文检索引擎工具包。
提供了完整的查询引擎和索引引擎,部分文本分析引擎。
Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能。
3.2 Lucene和搜索引擎的区别Lucene是一个工具包:需要进行编程才能实现全文检索的功能呢搜索引擎:一套独立运行的系统,提供了全文检索功能。
一步一步跟我学习lucene(1...自定义排序说明我们在做lucene搜索的时候,可能会需要排序功能,虽然lucene 内置了多种类型的排序,但是如果在需要先进行某些值的运算然后在排序的时候就有点显得无能为力了;要做自定义查询,我们就要研究lucene已经实现的排序功能,lucene的所有排序都是要继承FieldComparator,然后重写内部实现,这里以IntComparator为例子来查看其实现;IntComparator相关实现其类的声明为public static class IntComparator extends NumericComparator<Integer>,这里说明IntComparator接收的是Integer类型的参数,即只处理IntField的排序;IntComparator声明的参数为:[java] view plain copy1.private final int[] values;2.private int bottom; // Value of bottom of queue3.private int topValue;查看copy方法可知•values随着类初始化而初始化其长度•values用于存储NumericDocValues中读取到的内容具体实现如下:values的初始化[java] view plain copy1./**2.* Creates a new comparator based on {@link Integer#co mpare} for {@code numHits}.3.* When a document has no value for the field, {@code mi ssingValue} is substituted.4.*/5.public IntComparator(int numHits, String field, Integer mi ssingValue) {6.super(field, missingValue);7.values = new int[numHits];8.}values值填充(此为IntComparator的处理方式)[java] view plain copy1.@Override2.public void copy(int slot, int doc) {3.int v2 = (int) currentReaderValues.get(doc);4.// Test for v2 == 0 to save Bits.get method call for5.// the common case (doc has value and value is non-zero):6.if (docsWithField != null && v2 == 0 && !docsWithField. get(doc)) {7.v2 = missingValue;8.}9.10.values[slot] = v2;11.}这些实现都是类似的,我们的应用实现自定义排序的时候需要做的是对binaryDocValues或NumericDocValues的值进行计算,然后实现FieldComparator内部方法,对应IntComparator就是如上的值copy操作;然后我们需要实现compareTop、compareBottom和compare,IntComparator的实现为:[java] view plain copy1.@Override2.public int compare(int slot1, int slot2) {3.return pare(values[slot1], values[slot2]);4.}5.6.@Override7.public int compareBottom(int doc) {8.int v2 = (int) currentReaderValues.get(doc);9.// Test for v2 == 0 to save Bits.get method call for10.// the common case (doc has value and value is non-zero):11.if (docsWithField != null && v2 == 0 && !docsWithFi eld.get(doc)) {12.v2 = missingValue;13.}14.15.return pare(bottom, v2);16.}[java] view plain copy1.@Override2.public int compareTop(int doc) {3.int docValue = (int) currentReaderValues.get(doc);4.// Test for docValue == 0 to save Bits.get method call for5.// the common case (doc has value and value is non-zero):6.if (docsWithField != null && docValue == 0 && !docsWit hField.get(doc)) {7.docValue = missingValue;8.}9.return pare(topValue, docValue);10.}实现自己的FieldComparator要实现FieldComparator,需要对接收参数进行处理,定义处理值的集合,同时定义BinaryDocValues和接收的参数等,这里我写了一个通用的比较器,代码如下:[java] view plain copy1.package com.lucene.search;2.3.import java.io.IOException;4.5.import org.apache.lucene.index.BinaryDocValues;6.import org.apache.lucene.index.DocValues;7.import org.apache.lucene.index.LeafReaderContext;8.import org.apache.lucene.search.SimpleFieldComparator;9.10.import com.lucene.util.ObjectUtil;11.12./**自定义comparator13.* @author lenovo14.*15.*/16.public class SelfDefineComparator extends SimpleFie ldComparator<String> {17.private Object[] values;//定义的Object[],同IntComparator18.private Object bottom;19.private Object top;20.private String field;21.private BinaryDocValues binaryDocValues;//接收的BinaryDocValues,同IntComparator中的NumericDocValues22.private ObjectUtil objectUtil;//这里为了便于拓展用接口代替抽象类23.private Object[] params;//接收的参数24.25.public SelfDefineComparator(String field, int numHits, Object[] params,ObjectUtil objectUtil) {26.values = new Object[numHits];27.this.objectUtil = objectUtil;28.this.field = field;29.this.params = params;30.}31.32.@Override33.public void setBottom(int slot) {34.this.bottom = values[slot];35.}36.37.@Override38.public int compareBottom(int doc) throws IOExcepti on {39.Object distance = getValues(doc);40.return (bottom.toString()).compareTo(distance.toStrin g());41.}42.43.@Override44.public int compareTop(int doc) throws IOException {45.Object distance = getValues(doc);46.return pareTo(top,distance);47.}48.49.@Override50.public void copy(int slot, int doc) throws IOException {51.values[slot] = getValues(doc);52.}53.54./**��ȡdocID��Ӧ��value55.* @param doc56.* @return57.*/58.private Object getValues(int doc) {59.Object instance = objectUtil.getValues(doc,params,bin aryDocValues) ;60.return instance;61.}62.63.@Override64.protected void doSetNextReader(LeafReaderContext context)65.throws IOException {66.binaryDocValues = DocValues.getBinary(context.reade r(), field);//context.reader().getBinaryDocValues(field);67.}68.69.@Override70.public int compare(int slot1, int slot2) {71.return pareTo(values[slot1],values[slot2]);72.}73.@Override74.public void setTopValue(String value) {75.this.top = value;76.}77.78.@Override79.public String value(int slot) {80.return values[slot].toString();81.}82.83.}其中ObjectUtil是一个接口,定义了值处理的过程,最终是要服务于comparator的compare方法的,同时对comparator的内部compare方法进行了定义ObjectUtil接口定义如下:[java] view plain copy1.package com.lucene.util;2.3.import org.apache.lucene.index.BinaryDocValues;4.5.public interface ObjectUtil {6.7./**自定义的获取处理值的方法8.* @param doc9.* @param params10.* @param binaryDocValues11.* @return12.*/13.public abstract Object getValues(int doc, Object[] par ams, BinaryDocValues binaryDocValues) ;14.15./**compare比较器实现16.* @param object17.* @param object218.* @return19.*/20.public abstract int compareTo(Object object, Object object2);21.22.}我们不仅要提供比较器和comparator,同时还要提供接收用户输入的FiledComparatorSource[java] view plain copy1.package com.lucene.search;2.3.import java.io.IOException;4.5.import org.apache.lucene.search.FieldComparator;6.import org.apache.lucene.search.FieldComparatorSource;7.8.import com.lucene.util.ObjectUtil;9.10./**comparator用于接收用户原始输入,继承自FieldComparatorSource实现了自定义comparator的构建11.* @author lenovo12.*13.*/14.public class SelfDefineComparatorSource extends Fie ldComparatorSource {15.private Object[] params;//接收的参数16.private ObjectUtil objectUtil;//这里为了便于拓展用接口代替抽象类17.18.public Object[] getParams() {19.return params;20.}21.22.public void setParams(Object[] params) {23.this.params = params;24.}25.26.public ObjectUtil getObjectUtil() {27.return objectUtil;28.}29.30.public void setObjectUtil(ObjectUtil objectUtil) {31.this.objectUtil = objectUtil;32.}33.34.public SelfDefineComparatorSource(Object[] params, ObjectUtil objectUtil) {35.super();36.this.params = params;37.this.objectUtil = objectUtil;38.}39.40.@Override41.public FieldComparator<?> newComparator(String fie ldname, int numHits,42.int sortPos, boolean reversed) throws IOException {43.//实际比较由SelfDefineComparator实现44.return new SelfDefineComparator(fieldname, numHit s, params, objectUtil);45.}46.}相关测试程序,这里我们模拟一个StringComparator,对String 值进行排序[java] view plain copy1.package com.lucene.search;2.3.import org.apache.lucene.analysis.Analyzer;4.import org.apache.lucene.analysis.standard.StandardAnal yzer;5.import org.apache.lucene.document.BinaryDocValuesFiel d;6.import org.apache.lucene.document.Document;7.import org.apache.lucene.document.Field;8.import org.apache.lucene.document.StringField;9.import org.apache.lucene.index.DirectoryReader;10.import org.apache.lucene.index.IndexReader;11.import org.apache.lucene.index.IndexWriter;12.import org.apache.lucene.index.IndexWriterConfig;13.import org.apache.lucene.index.IndexWriterConfig.Op enMode;14.import org.apache.lucene.index.Term;15.import org.apache.lucene.search.IndexSearcher;16.import org.apache.lucene.search.MatchAllDocsQuery;17.import org.apache.lucene.search.Query;18.import org.apache.lucene.search.ScoreDoc;19.import org.apache.lucene.search.Sort;20.import org.apache.lucene.search.SortField;21.import org.apache.lucene.search.TermQuery;22.import org.apache.lucene.search.TopDocs;23.import org.apache.lucene.search.TopFieldDocs;24.import org.apache.lucene.store.RAMDirectory;25.import org.apache.lucene.util.BytesRef;26.27.import com.lucene.util.CustomerUtil;28.import com.lucene.util.ObjectUtil;29.import com.lucene.util.StringComparaUtil;30.31./**32.*33.* @author 吴莹桂34.*35.*/36.public class SortTest {37.public static void main(String[] args) throws Exceptio n {38.RAMDirectory directory = new RAMDirectory();39.Analyzer analyzer = new StandardAnalyzer();40.IndexWriterConfig indexWriterConfig = new IndexWri terConfig(analyzer);41.indexWriterConfig.setOpenMode(OpenMode.CREATE_ OR_APPEND);42.IndexWriter indexWriter = new IndexWriter(directory,indexWriterConfig);43.addDocument(indexWriter, "B");44.addDocument(indexWriter, "D");45.addDocument(indexWriter, "A");46.addDocument(indexWriter, "E");mit();48.indexWriter.close();49.IndexReader reader = DirectoryReader.open(directory) ;50.IndexSearcher searcher = new IndexSearcher(reader);51.Query query = new MatchAllDocsQuery();52.ObjectUtil util = new StringComparaUtil();53.Sort sort = new Sort(new SortField("name",new SelfD efineComparatorSource(new Object[]{},util),true));54.TopDocs topDocs = searcher.search(query, Integer.MA X_VALUE, sort);55.ScoreDoc[] docs = topDocs.scoreDocs;56.for(ScoreDoc doc : docs){57.Document document = searcher.doc(doc.doc);58.System.out.println(document.get("name"));59.}60.}61.62.private static void addDocument(IndexWriter writer,S tring name) throws Exception{63.Document document = new Document();64.document.add(new StringField("name",name,Field.Sto re.YES));65.document.add(new BinaryDocValuesField("name", new BytesRef(name.getBytes())));66.writer.addDocument(document);67.}68.}其对应的ObjectUtil实现如下:[java] view plain copy1.package com.lucene.util;2.3.import org.apache.lucene.index.BinaryDocValues;4.import org.apache.lucene.util.BytesRef;5.6.public class StringComparaUtil implements ObjectUtil {7.8.@Override9.public Object getValues(int doc, Object[] params,10.BinaryDocValues binaryDocValues) {11.BytesRef bytesRef = binaryDocValues.get(doc);12.String value = bytesRef.utf8T oString();13.return value;14.}15.16.@Override17.public int compareTo(Object object, Object object2) {18.// TODO Auto-generated method stub19.return object.toString().compareTo(object2.toString());20.}21.22.}。
搜索引擎Lucene第一章Lucene简介Lucene是apache软件基金会jakarta项目组的一个子项目,是一个开放源代码[的全文检索引擎工具包,即它不是一个完整的全文检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎(英文与德文两种西方语言)。
Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能,或者是以此为基础建立起完整的全文检索引擎。
第二章lucene索引的建立的五个基础类索引的建立,Lucene 提供了五个基础类,分别是Document, Field, IndexWriter, Analyzer, Directory。
以下是他们的用途:DocumentDocument的含义为文档,在Lucene中,它代表一种逻辑文件。
Lucene本身无法对物理文件建立索引,而只能识别并处理Document的类型文件。
Lucene从Document取出相关的数据源并根据属性配置进行相应的处理。
Field对象是用来描述一个文档的某个属性的lucene中的field也具有一些特定的类型如在中,Field内部包含两个静态的内部类分别是Store和Index详细的描述了Field的属性,它们分别表示Field的储存方式和索引方式。
Store类有3个公有的静态属性::表示该Field不需要储存。
:表示该Field需要储存。
:表示使用压缩方式来保存这个Field的值。
Index有4个公有的静态属性::表示该Field不需要索引,也就是用户不需要去查找该Field的值。
:表示该Field先被分词再被索引。
TOKENIZED:表示不对该Field进行分词,但是要对他进行索引,也就是该Field会被用户查找。
:表示对该Field进行索引,但是不使用Analyzer,同时禁止它参加评分,主要是为了减少内存的消耗。
Analyzer在一个文档被索引之前,首先需要对文档内容进行分词处理,这部分工作就是由Analyzer 来做的。
Lucene 3.5最新版在2011-11-26日发布了。
下载地址:/apache-mirror/lucene/java/3.5.0/Lucene进行了大量优化、改进和Bug的修复,包括:1.大大降低了控制开放的IndexReader上的协议索引的RAM占用(3~5倍)。
2.新增IndexSearcher.searchAfter,可在指定ScoreDoc后返回结果(例如之前页面的最后一个文档),以支持deep页用例。
3.新增SearcherManager,以管理共享和重新开始跨多个搜索线程的IndexSearchers。
基本的IndexReader实例如果不再进行引用,则会被安全关闭。
4.新增SearcherLifetimeManager,为跨多个请求(例如:paging/drilldown)的索引安全地提供了一个一致的视图。
5.将IndexWriter.optimize重命名为forceMerge,以便去阻止使用这种方法,因为它的使用代价较高,且也不需要使用。
6.新增NGramPhraseQuery,当使用n-gram分析时,可提升30%-50%的短语查询速度。
7.重新开放了一个API(IndexReader.openIfChanged),如果索引没有变化,则返回空值,而不是旧的reader。
8.Vector改进:支持更多查询,如通配符和用于产生摘要的边界分析。
9.修复了若干Bug。
针对做出一个简单的搜索引擎,笔者针对遇到的问题进行探讨:1.关于查询关键字的问题:String queryStr =”中国”;QueryParser queryParser = newMultiFieldQueryParser(Version.LUCENE_35, fields, luceneAnalyzer);Query query = queryParser.parse(queryString);Lucene对这个查询是不分大小写的,当搜索关键字为英文加数字或汉字或其他字符的时候,例如:“swing12”、“swing我sd”等,Lucene会先对这个关键字进行分词,即分成英文+数字或汉字的形式,然后去索引,这样docment中含有”swing”和”12”的Field都被索引出来了。
一步一步跟我学习lucene(1...这两天加班,不能兼顾博客的更新,请大家见谅。
有时候我们创建完索引之后,数据源可能有更新的内容,而我们又想像数据库那样能直接体现在查询中,这里就是我们所说的增量索引。
对于这样的需求我们怎么来实现呢?lucene内部是没有提供这种增量索引的实现的;这里我们一般可能会想到,将之前的索引全部删除,然后进行索引的重建。
对于这种做法,如果数据源的条数不是特别大的情况下倒还可以,如果数据源的条数特别大的话,势必会造成查询数据耗时,同时索引的构建也是比较耗时的,几相叠加,势必可能造成查询的时候数据缺失的情况,这势必严重影响用户的体验;比较常见的增量索引的实现是:•设置一个定时器,定时从数据源中读取比现有索引文件中新的内容或是数据源中带有更新标示的数据。
•对数据转换成需要的document并进行索引这样做较以上的那种全删除索引然后重建的好处在于:•数据源查询扫描的数据量小•相应的更新索引的条数也少,减少了大量的IndexWriter的commit和close这些耗时操作以上解决了增量的问题,但是实时性的问题还是存在的:•索引的变更只有在IndexWriter的commit执行之后才可以体现出来那么我们怎样对实时性有个提升呢,大家都知道lucene索引可以以文件索引和内存索引两种方式存在,相较于文件索引,内存索引的执行效率要高于文件索引的构建,因为文件索引是要频繁的IO操作的;结合以上的考虑,我们采用文件索引+内存索引的形式来进行lucene 的增量更新;其实现机制如下:•定时任务扫描数据源的变更•对获得的数据源列表放在内存中•内存中的document达到数量限制的时候,以队列的方式删除内存中的索引,并将之添加到文件索引•查询的时候采用文件+内存索引联合查询的方式以达到NRT效果定时任务调度器java内置了TimerT ask,此类是可以提供定时任务的,但是有一点就是TimerTask的任务是无状态的,我们还需要对任务进行并行的设置;了解到quartz任务调度框架提供了有状态的任务StatefulJob,即在本次调度任务没有执行完毕时,下次任务不会执行;常见的我们启动一个quartz任务的方式如下:[java] view plain copy1.Date runTime = DateBuilder.evenSecondDate(new Date()) ;2.StdSchedulerFactory sf = new StdSchedulerFactory();3.Scheduler scheduler = sf.getScheduler();4.JobDetail job = JobBuilder.newJob(XXX.class).build();5.Trigger trigger = TriggerBuilder.newTrigger().startAt(runTi me).withSchedule(SimpleScheduleBuilder.simpleSchedule().withI ntervalInSeconds(3).repeatForever()).forJob(job).build();6.scheduler.scheduleJob(job, trigger);7.8.scheduler.start();</span>以上我们是设置了每三秒执行一次定时任务,而任务类是XXX 任务类通用方法这里我定义了一个XXX的父类,其定义如下:[java] view plain copy1.package com.chechong.lucene.indexcreasement;2.3.import java.util.List;4.import java.util.TimerTask;5.6.import org.apache.lucene.store.RAMDirectory;7.import org.quartz.Job;8.import org.quartz.StatefulJob;9.10./**有状态的任务:串行执行,即不允许上次执行没有完成即开始本次如果需要并行给接口改为Job即可11.* @author lenovo12.*13.*/14.public abstract class BaseInCreasementIndex implem ents StatefulJob {15./**16.* 内存索引17.*/18.private RAMDirectory ramDirectory;19.public BaseInCreasementIndex() {20.}21.public BaseInCreasementIndex(RAMDirectory ramDire ctory) {22.super();23.this.ramDirectory = ramDirectory;24.}25.26./**更新索引27.* @throws Exception28.*/29.public abstract void updateIndexData() throws Excep tion;30./**消费数据31.* @param list32.*/33.public abstract void consume(List list) throws Excepti on;34.}任务类相关实现,以下方法是获取待添加索引的数据源XXXInCreasementIndex[java] view plain copy1.@Override2.public void execute(JobExecutionContext context) throw s JobExecutionException {3.try {4.XXXInCreasementIndex index = new XXXInCreasementIn dex(Constants.XXX_INDEX_PATH, XXXDao.getInstance(), RamDir ectoryControl.getRAMDireactory());5.index.updateIndexData();6.} catch (Exception e) {7.// TODO Auto-generated catch block8.e.printStackTrace();9.}10.}[java] view plain copy1.@Override2.public void updateIndexData() throws Exception {3.int maxBeanID = SearchUtil.getLastIndexBeanID();4.System.out.println(maxBeanID);5.List<XXX> sources = XXXDao.getListInfoBefore(maxBeanID);、、6.if (sources != null && sources.size() > 0) {7.this.consume(sources);8.}9.}这里,XXX代表我们要获取数据的实体类对象consume方法主要是做两件事:•数据存放到内存索引•判断内存索引数量,超出限制的话以队列方式取出超出的数量,并将之存放到文件索引[java] view plain copy1.@Override2.public void consume(List list) throws Exception {3.IndexWriter writer = RamDirectoryControl.getRAMIndex Writer();4.RamDirectoryControl.consume(writer,list);5.}上边我们将内存索引和队列的实现放在了RamDirectoryControl 中内存索引控制器首先我们对内存索引的IndexWriter进行初始化,在初始化的时候需要注意先执行一次commit,否则会提示no segments的异常[java] view plain copy1.private static IndexWriter ramIndexWriter;2.private static RAMDirectory directory;3.static{4.directory = new RAMDirectory();5.try {6.ramIndexWriter = getRAMIndexWriter();7.} catch (Exception e) {8.// TODO Auto-generated catch block9.e.printStackTrace();10.}11.}12.public static RAMDirectory getRAMDireactory(){13.return directory;14.}15.public static IndexSearcher getIndexSearcher() throw s IOException{16.IndexReader reader = null;17.IndexSearcher searcher = null;18.try {19.reader = DirectoryReader.open(directory);20.} catch (IOException e) {21. e.printStackTrace();22.}23.searcher = new IndexSearcher(reader);24.return searcher;25.}26./**单例模式获取ramIndexWriter27.* @return28.* @throws Exception29.*/30.public static IndexWriter getRAMIndexWriter() throw s Exception{31.if(ramIndexWriter == null){32.synchronized (IndexWriter.class) {33.Analyzer analyzer = new IKAnalyzer();34.IndexWriterConfig iwConfig = new IndexWriterConfig (analyzer);35.iwConfig.setOpenMode(OpenMode.CREATE_OR_APPE ND);36.try {37.ramIndexWriter = new IndexWriter(directory, iwConfig);mit();39.ramIndexWriter.close();40.iwConfig = new IndexWriterConfig(analyzer);41.iwConfig.setOpenMode(OpenMode.CREATE_OR_APPE ND);42.ramIndexWriter = new IndexWriter(directory, iwConfig);43.} catch (IOException e) {44.// TODO Auto-generated catch block45. e.printStackTrace();46.}47.}48.}49.50.return ramIndexWriter;51.}定义一个获取内存索引中数据条数的方法[java] view plain copy1./**根据查询器、查询条件、每页数、排序条件进行查询2.* @param query 查询条件3.* @param first 起始值4.* @param max 最大值5.* @param sort 排序条件6.* @return7.*/8.public static TopDocs getScoreDocsByPerPageAndSortFi eld(IndexSearcher searcher,Query query, int first,int max, Sort s ort){9.try {10.if(query == null){11.System.out.println(" Query is null return null ");12.return null;13.}14.TopFieldCollector collector = null;15.if(sort != null){16.collector = TopFieldCollector.create(sort, first+max, fal se, false, false);17.}else{18.SortField[] sortField = new SortField[1];19.sortField[0] = new SortField("createTime",SortField.Ty pe.STRING,true);20.Sort defaultSort = new Sort(sortField);21.collector = TopFieldCollector.create(defaultSort,first+ max, false, false, false);22.}23.searcher.search(query, collector);24.return collector.topDocs(first, max);25.} catch (IOException e) {26.// TODO Auto-generated catch block27.}28.return null;29.}此方法返回结果为T opDocs,我们根据TopDocs的totalHits来获取内存索引中的数据条数,以此来鉴别内存占用,防止内存溢出。
1.概述Lucene是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎。
Lucene以其方便使用、快速实施以及灵活性受到广泛的关注。
它可以方便地嵌入到各种应用中实现针对应用的全文索引、检索功能,本总结使用lucene--2.3.2。
2.lucene 的包结构1、org.apache.lucene.analysis对需要建立索引的文本进行分词、过滤等操作, 语言分析器,主要用于的切词Analyzer是一个抽象类,管理对文本内容的切分词规则。
2、org.apache.lucene.analysis.standard是标准分析器3、org.apache.lucene.document提供对Document和Field的各种操作的支持。
索引存储时的文档结构管理,类似于关系型数据库的表结构。
Document相对于关系型数据库的记录对象,Field主要负责字段的管理。
4、org.apache.lucene.index是最重要的包,用于向Lucene提供建立索引时各种操作的支持。
索引管理,包括索引建立、删除等。
索引包是整个系统核心,全文检索的根本就是为每个切出来的词建索引,查询时就只需要遍历索引,而不需要去正文中遍历,从而极大的提高检索效率。
5、org.apache.lucene.queryParser提供检索时的分析支持。
查询分析器,实现查询关键词间的运算,如与、或、非等。
6、org.apache.lucene.search 负责检索。
检索管理,根据查询条件,检索得到结果。
7、org.apache.lucene.store提供对索引存储的支持。
数据存储管理,主要包括一些底层的I/0操作。
8、org.apache.lucene.util提供一些常用工具类和常量类的支持3.索引文件格式a).fnm格式包含了Document中所有field名称b).fdt与.fdx格式.fdt文件用于存储具有Store.YES属性的Field的数据;.fdx是一个索引,用于存储Document在.fdt中的位置。
对于Lucene的索引过程,除了将词(Term)写入倒排表并最终写入Lucene的索引文件外,还包括分词(Analyzer)和合并段(merge segments)的过程,本次不包括这两部分,将在以后的文章中进行分析。
Lucene的索引过程,很多的博客,文章都有介绍,推荐大家上网搜一篇文章:《Annotated Lucene》,好像中文名称叫《Lucene源码剖析》是很不错的。
想要真正了解Lucene索引文件过程,最好的办法是跟进代码调试,对着文章看代码,这样不但能够最详细准确的掌握索引过程(描述都是有偏差的,而代码是不会骗你的),而且还能够学习Lucene的一些优秀的实现,能够在以后的工作中为我所用,毕竟Lucene是比较优秀的开源项目之一。
由于Lucene已经升级到3.0.0了,本索引过程为Lucene 3.0.0的索引过程。
一、索引过程体系结构Lucene 3.0的搜索要经历一个十分复杂的过程,各种信息分散在不同的对象中分析,处理,写入,为了支持多线程,每个线程都创建了一系列类似结构的对象集,为了提高效率,要复用一些对象集,这使得索引过程更加复杂。
其实索引过程,就是经历下图中所示的索引链的过程,索引链中的每个节点,负责索引文档的不同部分的信息,当经历完所有的索引链的时候,文档就处理完毕了。
最初的索引链,我们称之基本索引链。
为了支持多线程,使得多个线程能够并发处理文档,因而每个线程都要建立自己的索引链体系,使得每个线程能够独立工作,在基本索引链基础上建立起来的每个线程独立的索引链体系,我们称之线程索引链。
线程索引链的每个节点是由基本索引链中的相应的节点调用函数addThreads创建的。
为了提高效率,考虑到对相同域的处理有相似的过程,应用的缓存也大致相当,因而不必每个线程在处理每一篇文档的时候都重新创建一系列对象,而是复用这些对象。
所以对每个域也建立了自己的索引链体系,我们称之域索引链。
域索引链的每个节点是由线程索引链中的相应的节点调用addFields 创建的。
lucene:lucene学习笔记 3 各种query疯狂代码 / ĵ:http://DeveloperUtil/Article54058.html 1, 有时对于个Document来说有些Field会被频繁地操作而另些Field则不会这时可以将频繁操作Field和其他Field分开存放而在搜索时同时检索这两部分Field而提取出个完整Document 这要求两个索引包含Document数量必须相同 在创建索引时候可以同时创建多个IndexWriter将个Document根据需要拆分成多个包含部分FieldDocument并将这些Document分别添加到区别索引 而在搜索时则必须借助ParallelReader类来整合Directory dir1=FSDirectory.getDirectory( File(INDEX_DIR1),false);Directory dir2=FSDirectory.getDirectory( File(INDEX_DIR2),false);ParallelReader preader= ParallelReader;preader.add(IndexReader.open(dir1));preader.add(IndexReader.open(dir2));IndexSearcher searcher= IndexSearcher(preader); 的后操作和般搜索相同 2, Query子类. 下面几个搜索在各种区别要求场合,都会用到. 需要大家仔细研读!Query query1 = TermQuery( Term(FieldValue, "name1")); // 词语搜索Query query2 = WildcardQuery( Term(FieldName, "name*")); // 通配符Query query3 = PrefixQuery( Term(FieldName, "name1")); // 字段搜索 Field:Keyword自动在结尾添加 * Query query4 = RangeQuery( Term(FieldNumber, NumberTools.Long(11L)), Term(FieldNumber, NumberTools.Long(13L)), true); // 范围搜索Query query5 = FilteredQuery(query, filter); // 带过滤条件搜索Query query6 = MatchAllDocsQuery(... // 用来匹配所有文档Query query7 = FuzzyQuery (...模糊搜索Query query8 = RegexQuery (.. 正则搜索Query query9 = SpanRegexQuery(...) 同上, 正则表达式查询:Query query9 = SpanQuery 子类嵌套其他SpanQuery 增加了 rewrite思路方法Query query10 = DisjunctionMaxQuery ..类提供了针对某个短语最大score这点对多字段搜索非常有用 Query query11 = ConstantScoreQuery 类它包装了个 filter produces a scoreequal to the query boost for every matching document.BooleanQuery query12= BooleanQuery;booleanQuery.add(termQuery 1, BooleanClause.Occur.SHOULD);booleanQuery.add(termQuery 2, BooleanClause.Occur.SHOULD);//这个是为了联合多个查询而做Query类. BooleanQuery增加了最小匹配短语见:BooleanQuery.MinimumNumberShouldMatch. PhraseQuery 你可能对中日关系比较感兴趣想查找‘中’和‘日’挨得比较近(5个字距离内)文章超过这个距离不予考虑你可以:PhraseQuery query 13= PhraseQuery;query.Slop(5);query.add( Term("content ", “中”));query.add( Term(“content”, “日”)); PhraseQuery对于短语顺序是不管,这点在查询时除了提高命中率外,也会对性能产生很大影响, 利用SpanNearQuery可以对短语顺序进行控制,提高性能 BooleanQuery query12= SpanNearQuery 可以对短语顺序进行控制,提高性能 3, 索引文本文件 如果你想把纯文本文件索引起来而不想自己将它们读入串创建field你可以用下面代码创建field: Field field = Field("content", FileReader(file)); 这里file就是该文本文件该构造实际上是读去文件内容并对其进行索引但不存储 4, 如何删除索引 lucene提供了两种从索引中删除document思路方法种是 void deleteDocument( docNum) 这种思路方法是根据document在索引中编号来删除每个document加进索引后都会有个唯编号所以根据编号删除是种精确删除但是这个编号是索引内部结构般我们不会知道某个文件编号到底是几所以用处不大另种是 void deleteDocuments(Term term) 这种思路方法实际上是首先根据参数term执行个搜索操作然后把搜索到结果批量删除了我们可以通过这个思路方法提供个严格查询条件达到删除指定document目 下面给出个例子:Directory dir = FSDirectory.getDirectory(PATH, false);IndexReader reader = IndexReader.open(dir);Term term = Term(field, key);reader.deleteDocuments(term);reader.close; 5, 如何更新索引 lucene并没有提供专门索引更新思路方法我们需要先将相应document删除然后再将新document加入索引例如:Directory dir = FSDirectory.getDirectory(PATH, false);IndexReader reader = IndexReader.open(dir);Term term = Term(“title”, “lucene roduction”);reader.deleteDocuments(term);reader.close;IndexWriter writer = IndexWriter(dir, StandardAnalyzer, true);Document doc = Document;doc.add( Field("title", "lucene roduction", Field.Store.YES, Field.Index.TOKENIZED));doc.add( Field("content", "lucene is funny", Field.Store.YES, Field.Index.TOKENIZED));writer.addDocument(doc);writer.optimize;writer.close; 但是在1.9RC1中介绍说明: 新增类: org.apache.lucene.index.IndexModier 它合并了 IndexWriter 和 IndexReader好处是我们可以增加和删除文档时候区别担心 synchronisation/locking 问题了 6, filer类.使用 Filter 对搜索结果进行过滤可以获得更小范围内更精确结果 有人说: 注意它执行是预处理而不是对查询结果进行过滤所以使用filter代价是很大它可能会使次查询耗时提高百倍 ISOLatin1AccentFilter ,用 ISO Latin 1 集中unaccented类替代 accented 类 DateFilter 日期过滤器 RangeFileter ,比 DateFilter 更加通用实用 LengthFilter 类, 已经从 contrib 放到了 core 代码里从 stream 中去掉太长和太短单词 StopFilter 类, 增加了对处理stop words 忽略大小写处理 7,本条是个使用过滤介绍说明: 过滤 使用 Filter 对搜索结果进行过滤可以获得更小范围内更精确结果 举个例子我们搜索上架时间在 2005-10-1 到 2005-10-30 的间商品 对于日期时间我们需要转换下才能添加到索引库同时还必须是索引字段// indexdocument.Add(FieldDate, DateField.Date(date), Field.Store.YES, Field.Index.UN_TOKENIZED);//...// searchFilter filter = DateFilter(FieldDate, DateTime.Parse("2005-10-1"), DateTime.Parse("2005-10-30")); Hits hits = searcher.Search(query, filter); 除了日期时间还可以使用整数比如搜索价格在 100 ~ 200 的间商品 NumberTools 对于数字进行了补位处理如果需要使用浮点数可以自己参考源码进行// indexdocument.Add( Field(FieldNumber, NumberTools.Long((long)price), Field.Store.YES,Field.Index.UN_TOKENIZED));//...// searchFilter filter = RangeFilter(FieldNumber, NumberTools.Long(100L), NumberTools.Long(200L), true, true);Hits hits = searcher.Search(query, filter); 使用 Query 作为过滤条件QueryFilter filter = QueryFilter(QueryParser.Parse("name2", FieldValue, analyzer)); 我们还可以使用 FilteredQuery 进行多条件过滤Filter filter = DateFilter(FieldDate, DateTime.Parse("2005-10-10"), DateTime.Parse("2005-10-15")); Filter filter2 = RangeFilter(FieldNumber, NumberTools.Long(11L), NumberTools.Long(13L), true, true); Query query = QueryParser.Parse("name*", FieldName, analyzer);query = FilteredQuery(query, filter);query = FilteredQuery(query, filter2);IndexSearcher searcher = IndexSearcher(reader);Hits hits = searcher.Search(query); 8, Sort 有时你想要个排好序结果集就像SQL语句“order by”lucene能做到:通过Sort Sort sort = Sort(“time”); //相当于SQL“order by time” Sort sort = Sort(“time”, true); // 相当于SQL“order by time desc” 下面是个完整例子:Directory dir = FSDirectory.getDirectory(PATH, false);IndexSearcher is = IndexSearcher(dir);QueryParser parser = QueryParser("content", StandardAnalyzer);Query query = parser.parse("title:lucene content:lucene";RangeFilter filter = RangeFilter("time", "20060101", "20060230", true, true);Sort sort = Sort(“time”);Hits hits = is.search(query, filter, sort);for ( i = 0; i < hits.length; i){Document doc = hits.doc(i);.out.prln(doc.get("title");}is.close; 9, 性能优化 直到这里我们还是在讨论如何样使lucene跑起来完成指定任务利用前面说也确实能完成大部分功能但是测试表明lucene性能并不是很好在大数据量大并发条件下甚至会有半分钟返回情况另外大数据量数据化建立索引也是个十分耗时过程那么如何提高lucene性能呢?下面从优化创建索引性能和优化搜索性能两方面介绍 9.1 优化创建索引性能 这方面优化途径比较有限IndexWriter提供了些接口可以控制建立索引操作另外我们可以先将索引写入RAMDirectory再批量写入FSDirectory不管怎样目都是尽量少文件IO创建索引最大瓶颈在于磁盘IO另外选择个较好分析器也能提高些性能 9.1.1 通过设置IndexWriter参数优化索引建立 MaxBufferedDocs( maxBufferedDocs) 控制写入个新segment前内存中保存document数目设置较大数目可以加快建索引速度默认为10 MaxMergeDocs( maxMergeDocs) 控制个segment中可以保存最大document数目值较小有利于追加索引速度默认Integer.MAX_VALUE无需修改 MergeFactor( mergeFactor) 控制多个segment合并频率值较大时建立索引速度较快默认是10可以在建立索引时设置为100 9.1.2 通过RAMDirectory缓写提高性能 我们可以先把索引写入RAMDirectory达到定数量时再批量写进FSDirectory减少磁盘IO次数FSDirectory fsDir = FSDirectory.getDirectory("/data/index", true);RAMDirectory ramDir = RAMDirectory;IndexWriter fsWriter = IndexWriter(fsDir, StandardAnalyzer, true);IndexWriter ramWriter = IndexWriter(ramDir, StandardAnalyzer, true);while (there are documents to index){... create Document ...ramWriter.addDocument(doc);(condition for flushing memory to disk has been met){fsWriter.addIndexes( Directory { ramDir });ramWriter.close;ramWriter = IndexWriter(ramDir, StandardAnalyzer, true);}} 9.1.3 选择较好分析器 这个优化主要是对磁盘空间优化可以将索引文件减小将近半相同测试数据下由600M减少到380M但是对时间并没有什么帮助甚至会需要更长时间较好分析器需要匹配词库会消耗更多cpu测试数据用StandardAnalyzer耗时133分钟;用MMAnalyzer耗时150分钟 9.2 优化搜索性能 虽然建立索引操作非常耗时但是那毕竟只在最初创建时才需要平时只是少量维护操作更何况这些可以放到个后台进程处理并不影响用户搜索我们创建索引目就是给用户搜索所以搜索性能才是我们最关心下面就来探讨下如何提高搜索性能 9.2.1 将索引放入内存 这是个最直观想法内存比磁盘快很多Lucene提供了RAMDirectory可以在内存中容纳索引:Directory fsDir = FSDirectory.getDirectory(“/data/index/”, false);Directory ramDir = RAMDirectory(fsDir);Searcher searcher = IndexSearcher(ramDir); 但是实战证明RAMDirectory和FSDirectory速度差不多当数据量很小时两者都非常快当数据量较大时(索引文件400M)RAMDirectory甚至比FSDirectory还要慢点这确实让人出乎意料 而且lucene搜索非常耗内存即使将400M索引文件载入内存在运行段时间后都会out of memory所以个人认为载入内存作用并不大 9.2.2 优化时间范围限制 既然载入内存并不能提高效率定有其它瓶颈经过测试发现最大瓶颈居然是时间范围限制那么我们可以怎样使时间范围限制代价最小呢? 当需要搜索指定时间范围内结果时可以: 1、用RangeQuery设置范围但是RangeQuery实现实际上是将时间范围内时间点展开组成个个BooleanClause加入到BooleanQuery中查询 因此时间范围不可能设置太大经测试范围超过个月就会抛BooleanQuery.TooManyClauses可以通过设置BooleanQuery.MaxClauseCount( maxClauseCount)扩大但是扩大也是有限并且随着maxClauseCount扩大占用内存也扩大 2、用RangeFilter代替RangeQuery经测试速度不会比RangeQuery慢但是仍然有性能瓶颈查询90%以上时间耗费在RangeFilter研究其源码发现RangeFilter实际上是首先遍历所有索引生成个BitSet标记每个document在时间范围内标记为true不在标记为false然后将结果传递给Searcher查找这是十分耗时 3、进步提高性能这个又有两个思路: a、缓存CacheFilter结果既然RangeFilter执行是在搜索的前那么它输入都是定就是IndexReader而IndexReader是由Directory决定所以可以认为RangeFilter结果是由范围上下限决定也就是由具体RangeFilter对象决定所以我们只要以RangeFilter对象为键将filter结果BitSet缓存Cache起来即可luceneAPI已经提供了个CachingWrapperFilter类封装了Filter及其结果所以具体实施起来我们可以cache CachingWrapperFilter对象需要注意是不要被CachingWrapperFilter名字及其介绍说明误导CachingWrapperFilter看起来是有缓存Cache功能但缓存Cache是针对同个filter也就是在你用同个filter过滤区别IndexReader时它可以帮你缓存Cache区别IndexReader结果而我们需求恰恰相反我们是用区别filter过滤同个IndexReader所以只能把它作为个封装类 b、降低时间精度研究Filter工作原理可以看出它每次工作都是遍历整个索引所以时间粒度越大对比越快搜索时间越短在不影响功能情况下时间精度越低越好有时甚至牺牲点精度也值得当然最好情况是根本不作时间限制 下面针对上面两个思路演示下优化结果(都采用800线程随机关键词随即时间范围): 第组时间精度为秒: 方式 直接用RangeFilter 使用cache 不用filter 平均每个线程耗时 10s 1s 300ms 第 2组时间精度为天 方式 直接用RangeFilter 使用cache 不用filter 平均每个线程耗时 900ms 360ms 300ms 由以上数据可以得出结论: 1、 尽量降低时间精度将精度由秒换成天带来性能提高甚至比使用cache还好最好不使用filter 2、 在不能降低时间精度情况下使用cache能带了10倍左右性能提高 9.2.3 使用更好分析器 这个跟创建索引优化道理差不多索引文件小了搜索自然会加快当然这个提高也是有限较好分析器相对于最差分析器对性能提升在20%以下 10 些经验 10.1关键词区分大小写 or AND TO等关键词是区分大小写lucene只认大写小写当做普通单词 10.2 读写互斥性 同时刻只能有个对索引写操作在写同时可以进行搜索 10.3 文件锁 在写索引过程中强行退出将在tmp目录留下个lock文件使以后写操作无法进行可以将其手工删除 10.4 时间格式 lucene只支持种时间格式yyMMddHHmmss所以你传个yy-MM-dd HH:mm:ss时间给lucene它是不会当作时间来处理 10.5 设置boost 有些时候在搜索时某个字段权重需要大些例如你可能认为标题中出现关键词文章比正文中出现关键词文章更有价值你可以把标题boost设置更大那么搜索结果会优先显示标题中出现关键词文章(没有使用排序前题下)使用思路方法: Field. Boost(float boost);默认值是1.0也就是说要增加权重需要设置得比1大 上面这篇有关性能讲解是很深刻. 请学习.2009-1-15 22:30:33疯狂代码 /。