lucene学习教程
1Lucene的介绍
①Lucene是什么:
是一个开放源代码的全文检索引擎工具包,但它不是一个完整的全文检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎
②Lucene有什么用
Lucene是一个高性能、可伸缩的信息搜索(IR)库。它可以为你的应用程序添加索引和搜索能力,和对搜索词进行分析过滤
③Lucene怎么用
1 // Lucene使用步骤 2 // 1创建索引 3 // 1.1创建索引目录 4 Directory directory=FSDirectory.open(new File("indexDir")); 5 // 1.2创建indexWriter 6 IndexWriterConfig conf=new IndexWriterConfig(Version.LUCENE_35, new StandardAnalyzer(Version.LUCENE_35)); 7 IndexWriter indexWriter=new IndexWriter(directory, conf); 8 // 1.3创建document 9 Document document=new Document(); 10 // 1.4为document指定不同的域(Field) 11 document.add(new Field("fileName","java.txt",Store.YES,Index.ANALYZED)); 12 document.add(new NumericField("creatDate",Store.YES,true).setLongValue(new Date().getTime())); 13 document.add(new NumericField("size",Store.YES,true).setDoubleValue(10101.22)); 14 document.add(new Field("content",FileUtils.readFileToString(new File("java.txt")),Store.NO,Index.ANALYZED)); 15 // 1.5使用indexWriter.add(doc)方法,添加索引 16 indexWriter.addDocument(document); 17 // 1.6关闭indexWriter 18 indexWriter.close(); 19 // 2搜索索引 20 // 2.1指定索引存放位置 21 Directory indexDirectory=FSDirectory.open(new File("indexDir")); 22 // 2.2创建indexReader 23 IndexReader indexReader=IndexReader.open(indexDirectory); 24 // 2.3创建indexSearcher 25 IndexSearcher indexSearcher=new IndexSearcher(indexReader); 26 // 2.4创建query 27 Query query=new TermQuery(new Term("fileName","java")); 28 // 2.5根据indexSearcher.seacher(query,maxDoc);获取topDocs 29 TopDocs topDocs = indexSearcher.search(query, 100); 30 // 2.6根据topDocs获取ScoreDocs[] 31 ScoreDoc[] scoreDocs=topDocs.scoreDocs; 32 // 2.7遍历ScoreDocs[]获取docId 33 for (ScoreDoc scoreDoc : scoreDocs) { 34 int docId=scoreDoc.doc; 35 // 2.8根据docId调用indexSearcher.doc(docId)方法获取一个document 36 Document doc = indexSearcher.doc(docId); 37 // 2.9对document进行解析,获取需要的值 38 System.out.println("fileName-->"+document.get("fileName")+"createDate--->"+new Date(Long.parseLong(doc.get("createDate")))); 39 } 40 // 3.0关闭indexSearcher和indexReader 41 indexSearcher.close(); 42 indexReader.close();
2Lucene的组成
①索引
I索引建立的主要流程
1 // 1指定索引的存放目录 2 Directory directory=FSDirectory.open(new File("paht"));//硬盘 3 // //OR 4 Directory directory2=new RAMDirectory();//内存 5 // 2创建indexWriter 6 IndexWriterConfig conf=new IndexWriterConfig(Version.LUCENE_35, new StandardAnalyzer(Version.LUCENE_35)); 7 IndexWriter indexWriter=new IndexWriter(directory, conf); 8 // 3创建文档(document)(对于数据库而言,一个条记录就是一个文档,对于文件而言,一个文件就是一个文档) 9 // 1.3创建document 10 Document document=new Document(); 11 // 4为文档指定域(Field)(对于数据库而言,域相当于字段,对于文件而言域相当于属性) 12 document.add(new Field("fileName","java.txt",Store.YES,Index.ANALYZED)); 13 document.add(new NumericField("creatDate",Store.YES,true).setLongValue(new Date().getTime())); 14 document.add(new NumericField("size",Store.YES,true).setDoubleValue(10101.22)); 15 document.add(new Field("content",FileUtils.readFileToString(new File("java.txt")),Store.NO,Index.ANALYZED)); 16 // 5添加索引 17 indexWriter.addDocument(document);
II、重要类的介绍
directory
是用来指定索引的存放位置,可以是内存也可以是硬盘,FSDirectory.open(new File("paht")),会根据本地文件系统,自动选择一种最合适的方式存储索引
indexWriter
是用来对索引的进行增删改的重要操作类
Document
document对象对于数据库而言,一个条记录就是一个document,对于文件而言,一个文件就是一个document
Field
Field对象对于数据库而言,Field相当于字段(例如 name、age、、、、),对于文件而言Field相当于属性(例如文件名(name)..) 子类NumericField是用来存储数据类型的的字段的值,例如int 、long、double、,还有日期可以转换为long型后存储
II索引的增删改
①增加索引
indexWriter.addDocument(document);
②删除索引
1 //删除索引 2 indexWriter.deleteDocuments(new Term("fileName","java"));//删除文件名等于Java的document--删除后只是放在一个临时文档里,不被检索,并没有真正删除 3 indexWriter.forceMergeDeletes();//强制把删除的document删除掉
③更新索引
1 //更新索引--索引的更新原理:1根据query删除掉对应document,然后再把新的document放进去 2 indexWriter.updateDocument(new Term("fileName","java"), document);
III索引的权重
1 //①对于默认情况下,索引的排序是按照评分来排序的,评分公式是Score=Score*Boot , 2 //分数*权重,只要保证Boot的足够大,那么对应搜索的document就会排在第一位 3 //设置权重的办法是: 4 document.setBoost(1000F);
②分词
I分词运行流程分析
①searchWord首先会被Tokenizer分成一个一个的语汇单元,
②然后会经过一系列的TokenFilter(分词过滤器),过滤掉没意义的分词,例如“的,啊 ”这些感叹词
③经过一系列TokenFilter后,返回一个TokenStream,就是一个分词字符流,流里存有分词个各种信息
如下图:
II分词的类介绍
1Analyzer:分词器,是一个抽象类
1 //其主要包含两个接口,用于生成TokenStream: 2 TokenStream tokenStream(String fieldName, Reader reader); 3 TokenStream reusableTokenStream(String fieldName, Reader reader) ; 4 //为了提高性能,在同一个线程中无需再生成新的TokenStream对象,旧的可以被重用,reusableTokenStream是获取当前线程TokenSteam。
2Tokenizer
Tokenizer继承与TokenStream,是用来对searchWord的reader流进行分词,把searchWord分成一个一个的语汇单元
3TokenFilter
TokenFilter,过滤分词后的语汇单元,
主要方法incrementToken(),可以依次遍历语汇单元的信息
4TokenStream
分词字符流,流里存有分词个各种信息
例如:CharTermAttribute、OffsetAttribute、PositionIncrementAttribute、TypeAttribute、、等等
III扩展分词器
基本原理:就是使用自定义分词器的扩展原生analyzer的构造方法,然后用analyzer对应的Tokenizer分词,然后再使用自定义的TokenFilter过滤业务逻辑数据,
1自定义一个类继承analyzer
1 public class MyAnalyzer extends Analyzer {2}
2实现tokenStream方法
1 public class MyAnalyzer extends Analyzer { 2 @Override 3 public TokenStream tokenStream(String arg0, Reader reader) { 4 return null; 5 } 6 }
3自定义TokenFilter
package com.lucence.analyzer; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Stack; import org.apache.lucene.analysis.TokenFilter; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.tokenattributes.CharTermAttribute; import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute; /** * 自定义分词过滤器 * 1自定义类继承TokenFilter * 2实现指定的方法-incrementToken * 3在incrementToken里会遍历所有被分词的词汇单元, * 4实现自己的业务逻辑 * */ public class MyAnalyzerFilter extends TokenFilter { private CharTermAttribute cta; private PositionIncrementAttribute pia; private State state; private Stack<String> sameWordStack; protected MyAnalyzerFilter(TokenStream input) { super(input); this.cta=input.addAttribute(CharTermAttribute.class); this.pia=input.addAttribute(PositionIncrementAttribute.class); sameWordStack=new Stack<String>(); } @Override public boolean incrementToken() throws IOException { if(sameWordStack.size()>0){ String pop = sameWordStack.pop(); //恢复状态 restoreState(state); cta.setEmpty(); cta.append(pop); pia.setPositionIncrement(0); //System.out.print("["+cta+"]"+pia.getPositionIncrement()); System.out.println(state.hashCode()); return true; } if (!input.incrementToken()) { return false; } if(getSameWorder(cta.toString())){ //捕获当前状态 state=captureState(); } return true; } /** * 同义词处理--数据 */ private Boolean getSameWorder(String key){ //1申明一个map存放同义词---模拟数据库 Map<String, String[]> map=new HashMap<String, String[]>(); map.put("我",new String[]{"咱","吾","俺"}); map.put("中国",new String[]{"大陆","天朝"}); String[] strings = map.get(key); if(strings!=null&&strings.length>0){ for (int i = 0; i < strings.length; i++) { sameWordStack.push(strings[i]); } return true; }else{ return false; } } }
4使用自定义的TokenFilter返回处理后的TokenStream
public class MyAnalyzer extends Analyzer { @Override public TokenStream tokenStream(String arg0, Reader reader) { return new MyAnalyzerFilter(new IKTokenizer(reader,false)); } }
③搜索
I搜索的运行流程
// 1.1指定索引存放位置 Directory indexDirectory=FSDirectory.open(new File("indexDir")); // 1.2创建indexReader---indexReader.openIfChanged(oldReader),监听索引是否有改变,若索引有改变则重新获取indexReader IndexReader indexReader=IndexReader.open(indexDirectory); // 1.3创建indexSearcher IndexSearcher indexSearcher=new IndexSearcher(indexReader); // 1.4创建query Query query=new TermQuery(new Term("fileName","java")); // 1.5根据indexSearcher.seacher(query,maxDoc);获取topDocs TopDocs topDocs = indexSearcher.search(query, 100); // 1.6根据topDocs获取ScoreDocs[] ScoreDoc[] scoreDocs=topDocs.scoreDocs; // 1.7遍历ScoreDocs[]获取docId for (ScoreDoc scoreDoc : scoreDocs) { int docId=scoreDoc.doc; // 1.8根据docId调用indexSearcher.doc(docId)方法获取一个document Document doc = indexSearcher.doc(docId); // 1.9对document进行解析,获取需要的值 System.out.println("fileName-->"+doc.get("fileName")+"createDate--->"+new Date(Long.parseLong(doc.get("createDate")))); } // 2.0关闭indexSearcher和indexReader indexSearcher.close(); indexReader.close();
IIquery类的介绍
①termQuery:精确查询 new term(field,value) ②termRangeQuery:字符串范围查询new TermRangeQuery(field, lowerTerm, upperTerm, includeLower, includeUpper) ③NumericRange:数字范围查询,NumericRangeQuery.newTRange(field, min, max, minInclusive, maxInclusive)--T代表泛型 ④QueryParser: ⑤prefixQuery:前缀查询--new PrefixQuery(new term(field,prefix)) ⑥wildCartQuery:通配符查询--new wildCartQuery(new term(field,value))---value已经包含通配符,例如"*bb*",查找包含bb的数据 ⑦BooleanQuery:条件查询,可以连接多个多个条件 例如:
1 BooleanQuery booleanQuery=new BooleanQuery(); 2 booleanQuery.add(query1,occur) 3 booleanQuery.add(query2,occur) 4 booleanQuery.add(query3,occur)
occur的值说明
must:一定,必须有,相当于数据库的and
should:可能有,,相当于数据库的or
MUST_NOT:一定没有,不存在,相当于数据库的不等于
⑧FuzzyQuery:模糊查询,
new FuzzyQuery(term, minimumSimilarity),可以 设置minimumSimilarity来设置匹配程度,越高匹配程度越高, new FuzzyQuery(new term("name","bbcs"), minimumSimilarity)---含有bbc或者bbXs会被匹配出来
⑨phraseQuery:语义查询,对于中文,使用作用不大,
PhraseQuery phraseQuery=new PhraseQuery(); // 1设置跳跃的范围 phraseQuery.setSlop(2); //2设置开始的单词 phraseQuery.add(new Term("content","i")); // 3设置结束的单词 phraseQuery.add(new Term("content","you")); //例如包含i love you的内容将会出来
III扩展queryParse类
package com.lucence.query; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.queryParser.ParseException; import org.apache.lucene.queryParser.QueryParser; import org.apache.lucene.util.Version; /** * 扩展queryParse类 * queryParse的查询的原理:先对查询的字符串进行分析,然后再使用对应的query去查询, * 例如有通配符字符,就添加wildCartQuery去查询 ,如果*这些的,就添加FuzzyQuery去查询 * 也就说,如果我们想扩展queryParse的查询,那么可以自定义一个类,然后继承queryParse的,然后重构对应的getxxxquery()方法,并且在里面实现业务逻辑,则就可以实现扩展queryParse的功能 */ //1第一步自定义类继承Lucene的queryParse public class myQueryParse extends QueryParser { //2选择一个重写一个构造方法 public myQueryParse(Version matchVersion, String f, Analyzer a) { super(matchVersion, f, a); } //3重写对应的getXXQuery方法--并且在方法里实现业务逻辑 /** * field--搜索域 * termStr---搜索值 */ @Override protected org.apache.lucene.search.Query getWildcardQuery(String field, String termStr) throws ParseException { if(termStr.indexOf("?")!=-1){ throw new ParseException("不能使用通配符查询"); } return super.getWildcardQuery(field, termStr); } /** * field--搜索域 * termStr---搜索值 */ @Override protected org.apache.lucene.search.Query getFuzzyQuery(String field, String termStr, float minSimilarity) throws ParseException { return super.getFuzzyQuery(field, termStr, minSimilarity); } }
IV排序与分页
排序:
//①排序:默认是根据score排序,score默认是=score(关联性)*boot(权重) SortField sortField1=new SortField("fileName",SortField.STRING,true);//content--"字段名称", ,SortField.STRING-"字段在存放时的类型",true--是否反转 SortField sortField2=new SortField("size",SortField.INT,false);//content--"字段名称", ,SortField.BYTE-"字段在存放时的类型",true--是否反转 Sort sort=new Sort(sortField1,sortField2); TopDocs topDocs = searcher.search(query,10,sort);
分页:
//1searchAfter(scoreDocAfter, query, pagezie)方法是每次返回scoreDocAfter后面的document, int docId=(pagezie-1)*pageNumber-1;//每次查询是记录开始行 ScoreDoc scoreDocAfter=new ScoreDoc(docId,0f); TopDocs topDocs = searcher.searchAfter(scoreDocAfter, query, pagezie);
V搜索过滤器
package com.lucence.searchFilter; import java.io.IOException; import org.apache.lucene.document.Document; import org.apache.lucene.index.AbstractAllTermDocs; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.Term; import org.apache.lucene.index.TermDocs; import org.apache.lucene.search.DocIdSet; import org.apache.lucene.search.Filter; import org.apache.lucene.util.OpenBitSet; /** * 自定义搜索过滤器 * 1新建一个类继承Lucene的Filter * 2实现getDocIdSet方法 * 3根据indexReader获取到返回的document * 4根据自己的业务逻辑处理后返回DocIdSet * @author Jeremy * */ public class MySearchFilter extends Filter { @SuppressWarnings("unused") @Override public DocIdSet getDocIdSet(IndexReader indexReader) throws IOException { // TODO Auto-generated method stub int maxDoc = indexReader.maxDoc();//获取返回document的数目 OpenBitSet docIdSet=new OpenBitSet(maxDoc);//默认是64位大小,但是如果超出没报异常,所以一般在indexReader里获取返回document的大小 //docIdSet是一个相当于一个列表--如下面 //status document // 0 docId // 1 docId //若status是0 ,则document将不会被显示出来, //也就说,我们在filter把不需要的document可以过滤掉 //1填满列表---默认是空 docIdSet.set(0, maxDoc); //2获取返回的document //2.1直接使用indexReader来获取符合过滤条件的document //TermDocs---存储了两个变量,一个是返回的docId数组,一个每个document出现"javass.txt"的频率次数 TermDocs termDocs =indexReader.termDocs(new Term("content","jeremy")); while (termDocs.next()) { System.out.println(termDocs.doc()); Document document = indexReader.document(termDocs.doc()); System.out.println("fileName"+document.get("fileName")+"---出现频率:"+termDocs.freq()+"---被过滤掉了"); docIdSet.clear(termDocs.doc());//clear()方法相当于把status设置为0 } return docIdSet; } }
VI自定义评分
实现步骤:
* 自定义评分
* 默认的评分机制是 score=score*Root = 分数*索引的权重
* 自定义评分的实现流程
* 1新建一个类及承诺CustomScoreQuery
* 2覆盖CustomScoreQuery(Query subQuery, ValueSourceQuery valSrcQuery)的构造方法--ValueSourceQuery:评分域查询对象
* 3覆盖 getCustomScoreProvider(IndexReader reader)方法
* 4新建一个类继承CustomScoreProvider
* 5覆盖CustomScoreProvider的customScore(int doc, float subQueryScore, float valSrcScore)方法,
* --------doc:docId,subQueryScore:原有评分,valSrcScore:我们自定义传入的评分
* 6在customScore(int doc, float subQueryScore, float valSrcScore)方法里返回经过业务逻辑处理的的自定义评分
* 7在getCustomScoreProvider返回自定的义的MyCustomScoreProvider对象
* 8在查询中使用MyCustomerScroeQuery
代码示例:
package com.lucence.scoreQuery;
import java.io.IOException; import org.apache.lucene.index.IndexReader; import org.apache.lucene.search.Query; import org.apache.lucene.search.function.CustomScoreProvider; import org.apache.lucene.search.function.CustomScoreQuery; import org.apache.lucene.search.function.ValueSourceQuery; /** * 自定义评分 * 默认的评分机制是 score=score*Root = 分数*索引的权重 * 自定义评分的实现流程 * 1新建一个类及承诺CustomScoreQuery * 2覆盖CustomScoreQuery(Query subQuery, ValueSourceQuery valSrcQuery)的构造方法--ValueSourceQuery:评分域查询对象 * 3覆盖 getCustomScoreProvider(IndexReader reader)方法 * 4新建一个类继承CustomScoreProvider * 5覆盖CustomScoreProvider的customScore(int doc, float subQueryScore, float valSrcScore)方法, * --------doc:docId,subQueryScore:原有评分,valSrcScore:我们自定义传入的评分 * 6在customScore(int doc, float subQueryScore, float valSrcScore)方法里返回经过业务逻辑处理的的自定义评分 * 7在getCustomScoreProvider返回自定的义的MyCustomScoreProvider对象 * 8在查询中使用MyCustomerScroeQuery * @author Jeremy * */ // 1新建一个类及承诺CustomScoreQuery public class MyCustomerScroeQuery extends CustomScoreQuery { //2覆盖CustomScoreQuery(Query subQuery, ValueSourceQuery valSrcQuery)的构造方法--ValueSourceQuery:评分域查询对象 public MyCustomerScroeQuery(Query subQuery, ValueSourceQuery valSrcQuery) { super(subQuery, valSrcQuery); } public MyCustomerScroeQuery(Query subQuery) { super(subQuery); } @Override //3覆盖 getCustomScoreProvider(IndexReader reader)方法 protected CustomScoreProvider getCustomScoreProvider(IndexReader reader)throws IOException { //7在getCustomScoreProvider返回自定的义的MyCustomScoreProvider对象 return new MyCustomScoreProvider(reader); } }
package com.lucence.scoreQuery; import java.io.IOException; import org.apache.lucene.document.Document; import org.apache.lucene.index.IndexReader; import org.apache.lucene.search.function.CustomScoreProvider; // 4新建一个类继承CustomScoreProvide public class MyCustomScoreProvider extends CustomScoreProvider{ public MyCustomScoreProvider(IndexReader reader) { super(reader); } @Override /** * 5覆盖CustomScoreProvider的customScore(int doc, float subQueryScore, float valSrcScore)方法, * --------doc:docId,subQueryScore:原有评分,valSrcScore:我们自定义传入的评分 */ //subQueryScore * valSrcScore;---默认的评分是原有的评分乘以评分域的的值 public float customScore(int doc, float subQueryScore, float valSrcScore) throws IOException { //6在customScore方法里返回业务逻辑处理后的自定义评分 System.out.println(subQueryScore+"------"+valSrcScore); Document document = reader.document(doc); if(document.get("fileName").endsWith(".txt")){//文件类型为。txt的优先排序 return subQueryScore*valSrcScore*100; } return super.customScore(doc, subQueryScore, valSrcScore); } }
使用代码示例:
public void test01(){ //3.1指定搜索目录 try { Directory directory=FSDirectory.open(new File("C:/lucence/index")); //3.2创建索引读取器(IndexReader) IndexReader indexReader=IndexReader.open(directory); //3.3根据IndexReader创建索引搜索器(indexSeacher) IndexSearcher searcher=new IndexSearcher(indexReader); //3.4创建查询器query----使用QueryParser的parser()方法创建--创建query Query query=null; QueryParser parser=new QueryParser(Version.LUCENE_35,"content",new StandardAnalyzer(Version.LUCENE_35)); query=parser.parse("spring"); // //3.4.2c创建评分域---可以使用评分域去评分,也可以不使用----评分域的得类型必须是数据类型--- // FieldScoreQuery fieldScoreQuery=new FieldScoreQuery("fileName",Type.BYTE); // //3.4.3使用MyCustomerScroeQuery来构建query // MyCustomerScroeQuery myCustomerScroeQuery = new MyCustomerScroeQuery(query, fieldScoreQuery); MyCustomerScroeQuery myCustomerScroeQuery=new MyCustomerScroeQuery(query); //3.5使用自定义的myCustomerScroeQuery进行查询过,IndexSeacher执行查询,并获取返回TopDocs---文档集合 TopDocs topDocs = searcher.search(myCustomerScroeQuery,100); //3.6根据TopDocs(文档集合)获取scoreDocs---分数文档 ScoreDoc[] scoreDocs=topDocs.scoreDocs; for (ScoreDoc scoreDoc : scoreDocs) { //3.7根据ScoreDocs的doc Id在indexSeacher(索引搜索器)中获取文档对象, Document doc = searcher.doc(scoreDoc.doc); //3.8解析文档对象,获取对应的值 System.out.println(doc.get("fileName")+"["+doc.get("dir")+"]"+doc.getBoost()); } //3.9关闭索引读取器 indexReader.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ParseException e) { // TODO Auto-generated catch block e.printStackTrace(); } }