本文共 13353 字,大约阅读时间需要 44 分钟。
今日在学习Lucene是遇到了一些因版本更新出现的问题,通过学习别人的博客将问题解决,现分享一下。
lucene的创建索引,查询索引,搜索排序,通过权值查询,以及适配新版本的luke查询器,IK中文分词,高亮显示等最基本的使用!
org.apache.lucene lucene-core 7.1.0 org.apache.lucene lucene-analyzers-common 7.1.0 org.apache.lucene lucene-queryparser 7.1.0 org.apache.lucene lucene-highlighter 7.1.0 org.apache.lucene lucene-analyzers-smartcn 7.1.0 commons-io commons-io 2.4
IndexWriter writer=null;// 1 指定索引库存放路径// 硬盘路径try { //好像是从5.x版本中打开目录就必须用Path类了,方法如下,不能像4.x版本之前的直接写路径 File indexrepository_file = new File("D:\\others\\lucene\\index"); Path path = indexrepository_file.toPath(); Directory directory=null; directory = FSDirectory.open(path);//使用path // 索引建立在内存中 //Directory directory01=new RAMDirectory(); // 2 创建writer //指定一个分词器 Analyzer analyzer=new IKAnalyzer();//使用IK,需要用特定的高版本适配版 IndexWriterConfig config=new IndexWriterConfig(analyzer); writer=new IndexWriter(directory,config); // 3 创建document对象,并添加Filed域属性 Document doc=null; File f=new File("D:\\others\\lucene\\doc");//文档目录 for(File file : f.listFiles()){//遍历每个文档 doc=new Document(); /** * 新版本中使用了Int/Long/DoublePoint来表示数值型字段,但是默认不存储,不排序,也不支持加权 * 创建索引加权值在6.6版本后就已经废除了,并给了搜索时设置的新query,这个后面查询时再说 * 如果存储需要用StoredField写相同的字段,排序还要再使用NumericDocValuesField写相同的排序, * 如下的fileSize,添加long值索引,存储并添加排序支持 / //文件名 doc.add(new TextField("fileName", file.getName(), Store.YES);); //大小,数字类型使用point添加到索引中,同时如果需要存储,由于没有Stroe,所以需要再创建一个StoredField进行存储 // 即 IntPoint,DoublePoint等 doc.add(new LongPoint("fileSize", file.length())); //大小 doc.add(new StoredField("fileSize", file.length())); //同时添加排序支持 doc.add(new NumericDocValuesField("fileSize",file.length())); //路径 doc.add(new StoredField("filePath",file.getPath())); //内容 doc.add(new TextField("fileContent",FileUtils.readFileToString(file),Store.NO)); //doc.add(new TextField("fileContent",new FileReader(file))); // 4 使用indexWriter将doc对象写入索引库,此过程创建索引,并将索引和文档对象写入索引库 writer.addDocument(doc); } writer.close(); } catch (IOException e) { e.printStackTrace();}
7.1.0版本中数值型的都使用Point了,所以Long被废弃,
使用luke查询索引:
luke是一个查询索引的工具,使用时必须注意:版本要与lucene的版本完全一致,否则可能打不开索引信息
这里先举个简单例子,使用内容关键字查询,通用使用Path类打开索引目录,其他区别不是很大:
try { //1 创建Directory对象,索引存放位置 File indexrepository_file = new File("D:\\others\\lucene\\index"); Path path = indexrepository_file.toPath(); Directory directory = FSDirectory.open(path); //2 创建IndexReader IndexReader indexReader = DirectoryReader.open(directory); //3 创建IndexSearch对象 IndexSearcher indexSearch=new IndexSearcher(indexReader); //4 创建TermQuery对象,指定查询的域和关键字 Query query=new TermQuery(new Term("fileContent","姚振")); //5 查询 TopDocs topDocs = indexSearch.search(query, 5);//前5个 //6 遍历结果 ScoreDoc[] scoreDocs = topDocs.scoreDocs;//文档id数组 for (ScoreDoc scoreDoc : scoreDocs) { //根据id获取文档 Document doc = indexSearch.doc(scoreDoc.doc); //获取结果,没有存储的是null,比如内容 System.out.println("文档名: "+doc.get("fileName")); System.out.println("文档路径: "+doc.get("filePath")); System.out.println("文档大小: "+doc.get("fileSize")); System.out.println("文档内容: "+doc.get("fileContent")); System.out.println("-------------------"); } //7 关闭reader indexReader.close();} catch (IOException e) { e.printStackTrace();}
查询主要有两种,一种是Query的子类查询,一种是语法解析查询,这里先说子类查询:
可以先写一个获取search的公共方法,以及遍历打印搜索结果的方法,之后就可以对各种查询进行方便测试了:
//获取IndexWriterpublic IndexWriter getIndexWriter() throws Exception{ File indexrepository_file = new File("D:\\others\\lucene\\index"); Path path = indexrepository_file.toPath(); Directory directory=null; directory = FSDirectory.open(path); Analyzer analyzer=new IKAnalyzer();//使用IK IndexWriterConfig config=new IndexWriterConfig(analyzer); return new IndexWriter(directory,config);}
//获取IndexSearch public IndexSearcher getIndexSearcher() throws Exception{ return new IndexSearcher(getIndexReader()); }
//执行查询并打印结果public void printResult(IndexSearcher indexSearcher,Query query,Integer num) throws Exception{ //使用排序 Sort sort =new Sort(); SortField f =new SortField("fileSize",Type.LONG,true); // 按照fileSize字段排序,true表示降序 sort.setSort(f); // 多个条件排序 // Sort sort = new Sort(); // SortField f1 = new SortField("createdate", SortField.DOC, true); // SortField f2 = new SortField("bookname", SortFiedl.INT, false); // sort.setSort(new SortField[] { f1, f2 }); //高亮显示start //算分 QueryScorer scorer=new QueryScorer(query); //显示得分高的片段 Fragmenter fragmenter=new SimpleSpanFragmenter(scorer); //设置标签内部关键字的颜色 //第一个参数:标签的前半部分;第二个参数:标签的后半部分。 SimpleHTMLFormatter simpleHTMLFormatter=new SimpleHTMLFormatter("",""); //第一个参数是对查到的结果进行实例化;第二个是片段得分(显示得分高的片段,即摘要) Highlighter highlighter=new Highlighter(simpleHTMLFormatter, scorer); //设置片段 highlighter.setTextFragmenter(fragmenter); //高亮显示end TopDocs topDocs = indexSearcher.search(query, num,sort); ScoreDoc[] scoreDocs = topDocs.scoreDocs;//文档id数组 for (ScoreDoc scoreDoc : scoreDocs) { //根据id获取文档 Document doc = indexSearcher.doc(scoreDoc.doc); String name = doc.get("fileName"); if(name!=null){ //把全部得分高的摘要给显示出来 //第一个参数是对哪个参数进行设置;第二个是以流的方式读入 TokenStream tokenStream=new IKAnalyzer().tokenStream("fileName", new StringReader(name)); //获取最高的片段 System.out.println("高亮文档名: "+highlighter.getBestFragment(tokenStream, name)); } //获取结果,没有存储的是null,比如内容 System.out.println("文档名: "+doc.get("fileName")); System.out.println("文档路径: "+doc.get("filePath")); System.out.println("文档大小: "+doc.get("fileSize")); System.out.println("文档内容: "+doc.get("fileContent")); System.out.println("-------------------"); } }
//查询@Testpublic void testSelect01() throws Exception{ IndexSearcher indexSearcher = getIndexSearcher(); //数字范围查询, 两边都是闭区间 43< index <103 //新版本中数值都使用Point进行查询,原理的Numic被废弃 //Query query=LongPoint.newRangeQuery("fileSize", 43L, 103L); //数值精确匹配,只会查找参数里的数值索引 index in param Listlist=new ArrayList (); list.add(43L); list.add(103L); Query query2=LongPoint.newSetQuery("fileSize", 43L,100L);//不定参数 Query query1=LongPoint.newSetQuery("fileSize", list); //集合参数 printResult(indexSearcher,query1,10); //关闭reader indexSearcher.getIndexReader().close();}/** * TermRangeQuery是用于字符串范围查询的,既然涉及到范围必然需要字符串比较大小, * 字符串比较大小其实比较的是ASC码值,即ASC码范围查询。 * 一般对于英文来说,进行ASC码范围查询还有那么一点意义, * 中文汉字进行ASC码值比较没什么太大意义,所以这个TermRangeQuery了解一下就行 */@Testpublic void testSelect02() throws Exception{ String lowerTermString = "侯征";//范围的下端的文字,后面boolean为真,对应值为闭区间 String upperTermString = "词语";//范围的上限内的文本,后面boolean为真,对应值为闭区间 IndexSearcher indexSearcher = getIndexSearcher(); //lucene 使用 BytesRef 在索引中表示utf-8编码的字符,此类含有偏移量_长度以及byte数组,可使用utf8toString API转换字符串 Query query=new TermRangeQuery("fileName",new BytesRef(lowerTermString),new BytesRef(upperTermString),true,true); printResult(indexSearcher,query,10); //关闭reader indexSearcher.getIndexReader().close();}/** * BooleanQuery也是实际开发过程中经常使用的一种Query。 * 它其实是一个组合的Query,在使用时可以把各种Query对象添加进去并标明它们之间的逻辑关系。 * 所有的Query都可以通过booleanQUery组合起来 * BooleanQuery本身来讲是一个布尔子句的容器,它提供了专门的API方法往其中添加子句, * 并标明它们之间的关系 */@Testpublic void testSelect03() throws Exception{ IndexSearcher indexSearcher = getIndexSearcher(); //组合条件 Query query1=new TermQuery(new Term("fileName","歌曲")); Query query2=new TermQuery(new Term("fileContent","美国")); //相当于一个包装类,将 Query 设置 Boost 值 ,然后包装起来。 //再通过复合查询语句,可以突出 Query 的优先级 BoostQuery query=new BoostQuery(query2, 2f); //创建BooleanQuery.Builder BooleanQuery.Builder builder=new BooleanQuery.Builder(); //添加逻辑 /** * 1.MUST和MUST:取得两个查询子句的交集。 and 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:无意义,检索无结果。 */ builder.add(query1, Occur.SHOULD);// 文件名不包含词语,但是内容必须包含姚振 builder.add(query, Occur.SHOULD); //build query BooleanQuery booleanQuery=builder.build(); printResult(indexSearcher,booleanQuery,10); //关闭reader indexSearcher.getIndexReader().close();}@Testpublic void testSelect() throws Exception{ IndexSearcher indexSearcher = getIndexSearcher(); //查询所有 Query queryAll=new MatchAllDocsQuery(); printResult(indexSearcher,queryAll,10); //关闭reader indexSearcher.getIndexReader().close();}@Testpublic void test05() throws Exception{ IndexSearcher indexSearcher = getIndexSearcher(); //查询文件名以新开头的索引 前缀匹配查询 Query query=new PrefixQuery(new Term("fileName","新")); System.out.println(query); printResult(indexSearcher,query,10); //关闭reader indexSearcher.getIndexReader().close();}/** * PhraseQuery,是指通过短语来检索,比如我想查“姚振 牛逼”这个短语, * 那么如果待匹配的document的指定项里包含了"姚振 牛逼"这个短语, * 这个document就算匹配成功。可如果待匹配的句子里包含的是“姚振 真他妈 牛逼”, * 那么就无法匹配成功了,如果也想让这个匹配,就需要设定slop, * 先给出slop的概念:slop是指两个项的位置之间允许的最大间隔距离 */@Testpublic void test06() throws Exception{ IndexSearcher indexSearcher = getIndexSearcher(); Builder build = new PhraseQuery.Builder(); build.add(new Term("fileContent","白富美")); build.add(new Term("fileContent","老阴逼")); //设置slop,即最大相隔多远,即多少个文字的距离, build.setSlop(6);//表示如果这两个词语相隔6个字以下的位置就匹配 PhraseQuery phraseQuery = build.build(); printResult(indexSearcher,phraseQuery,10); //关闭reader indexSearcher.getIndexReader().close();}@Testpublic void test07() throws Exception{ IndexSearcher indexSearcher = getIndexSearcher(); //FuzzyQuery是一种模糊查询,它可以简单地识别两个相近的词语 Query query=new FuzzyQuery(new Term("fileContent","牛逼")); printResult(indexSearcher,query,10); //关闭reader indexSearcher.getIndexReader().close();}@Testpublic void test09() throws Exception{ IndexSearcher indexSearcher = getIndexSearcher(); //Lucene也提供了通配符的查询,这就是WildcardQuery。 // 通配符“?”代表1个字符,而“*”则代表0至多个字符。 Query query=new WildcardQuery(new Term("fileName","?词语")); //名字以词语结尾 Query query1=new WildcardQuery(new Term("fileName","新*")); //名字以新开头 printResult(indexSearcher,query1,10); //关闭reader indexSearcher.getIndexReader().close();}
@Testpublic void test08() throws Exception{ /** * 解析查询表达式 * QueryParser实际上就是一个解析用户输入的工具,可以通过扫描用户输入的字符串,生成Query对象 */ IndexSearcher indexSearcher = getIndexSearcher(); Query query=new TermQuery(new Term("fileName","词语")); // 参数: 默认域 分词解析器 QueryParser queryParser = new QueryParser("fileContent", new IKAnalyzer()); //解析 ,如果不指定域,使用默认域 使用语法书写 Query parse = queryParser.parse("侯征 姚振 何毅"); Query parse1 = queryParser.parse("fileName:侯征 姚振 何毅");//指定域 Query parse2 = queryParser.parse("fileName:侯*");//匹配 Query parse3 = queryParser.parse("+fileName:游戏 fileName:新词语");//匹配 printResult(indexSearcher,parse3,10); //关闭reader indexSearcher.getIndexReader().close();}@Testpublic void test10() throws Exception{ /** * 解析查询表达式 * MultiFieldQueryParser支持多默认域 */ IndexSearcher indexSearcher = getIndexSearcher(); // 指定多默认域数组 String[] arr=new String[]{"fileName","fileContent"}; //搜索时设置权重 Mapboosts = new HashMap (); boosts.put("fileContent", 10.0f);//权重默认是1, 文件名字符合条件的排序在前面 MultiFieldQueryParser queryParser = new MultiFieldQueryParser(arr, new IKAnalyzer(),boosts);//指定搜索权重 //解析 ,如果不指定域,使用默认域 使用语法书写 Query parse = queryParser.parse("游戏");//查询所有默认域里有姚振的文档 printResult(indexSearcher,parse,10); //关闭reader indexSearcher.getIndexReader().close();}
还有一点,就是加权了,查询时设置权重,6.6版本后创建索引设置权重值已经被废弃,并提供了两种新的加权方式就是BoostsQuery 和语法查询时多默认域的扩展可以使用,上面例子里有,可自行选择。
最后就是索引的更新,删除,恢复了,这个没多大区别,主要是创建,查询时候有恨多不一样的地方,还有一点就是文档的编码一定要对,否则会索引不到。
//按条件删除@Testpublic void testDelete() throws Exception{ IndexWriter indexWriter = getIndexWriter(); Query query=new TermQuery(new Term("fileContent","老阴逼")); indexWriter.deleteDocuments(query);//可传入多个参数 indexWriter.close();}//全删除@Testpublic void testDeleteAll() throws Exception{ IndexWriter indexWriter = getIndexWriter(); indexWriter.deleteAll(); //强制删除,不会恢复 //indexWriter.forceMergeDeletes(); indexWriter.close();}//更新@Testpublic void testUpdate() throws Exception{ IndexWriter indexWriter = getIndexWriter(); Document doc =new Document(); doc.add(new TextField("","",Store.YES)); // .... 修改的内容 indexWriter.updateDocument(new Term("fileName","词语"), doc); indexWriter.close();} //恢复,从回收站恢复@Testpublic void testDELETE() throws Exception{ IndexWriter indexWriter = getIndexWriter(); indexWriter.rollback();}
转自:
转载地址:http://icosi.baihongyu.com/