Lucene4.X 高级应用
Lucene 是 Apache 软件基金会 jakarta 项目组的一个子项目,它是一个基于 Java 的全文信息检索工具包,但不是一个完整的搜索应用程序,而是为你的应用程序提供索引和搜索功能。Lucene 旨在为开发人员提供一个简单易用的搜索工具包,方便在目标系统中进行全文检索。
Lucene 简介以及使用
Lucene,一个基于 Java 的开源的全文搜索工具包,可以方便的嵌入到各种应用系统中,实现针对应用的全文索引以及检索功能。目前是 Apache jakarta 项目组的一个子项目,它的目的是为程序员提供工具包,让程序员利用工具包里的强大接口来完成全文检索。下面我们将以 Lucene4.7 版本为例,为您详细讲解索引的创建、创建时的参数配置、Lucene4.7 版本的各种 query 查询、Lucene 神器 Luke 的使用等内容。
准备工作
本文需要的 jar 包:
lucene-analyzers-common-4.7.0.jar
lucene-core-4.7.0.jar
lucene-queryparser-4.7.0.jar
Lucene 相关问题可以参考Lucene 的官方 API。
Lucene 常用包官方网站下载。
重要关键字介绍
IndexWriter:用于处理索引,如增加、更改或者删除索引。
FSDirectory:索引目录,除此之外还有一个 Ramdirectry。FSDirectory 是将索引创建到磁盘里,而 Ramdirectry 是将索引创建到内存里。
IndexWriterConfig:这里可配置版本号,分词器,打开模式等等,合理的应用该对象属性可以大大提高创建索引的性能。
Document:文档,我们将每个字段放在 document 里。
Field :域,类似数据库中的 column。
Lucene 实战
创建索引
首先我们介绍下如何创建索引。相关步骤分为:建立索引器 IndexWriter,建立文档对象 Document,建立信息字段对象 Field,将 Field 添加到 Document,将 Document 添加到 IndexWriter 里面,最后不要忘记关闭 IndexWriter。
清单 1. 建立索引
package lucene; …… public class IndexUtil { private String[] idArr = {"1","2","3","4","5","6"}; private String[] emailArr = {"abc@us.ibm.com","ert@cn.ibm.com","lucy@us.ibm.com", "rock@cn.ibm.com","test@126.com","deploy@163.com"}; private String[] contentArr = { "welcome to Lucene,I am abc","This is ert,I am from China", "I'm Lucy,I am english","I work in IBM", "I am a tester","I like Lucene in action" }; private String[] nameArr = {"abc","ert","lucy","rock","test","deploy"}; private Directory directory = null; public void index() { IndexWriter writer = null; try { directory = FSDirectory.open(new File("C:/lucene/index02")); IndexWriterConfig conf = new IndexWriterConfig(Version.LUCENE_47, new StandardAnalyzer(Version.LUCENE_47)); conf.setOpenMode(OpenMode.CREATE_OR_APPEND); LogMergePolicy mergePolicy = new LogDocMergePolicy(); mergePolicy.setMergeFactor(10); mergePolicy.setMaxMergeDocs(10); conf.setMaxBufferedDocs(10); writer = new IndexWriter(directory, conf); Document doc = null; int date = 1; for(int i=0;i<idArr.length;i++) { doc = new Document(); doc.add(new StringField("id",idArr[i],Field.Store.YES)); doc.add(new StringField("email",emailArr[i],Field.Store.YES)); doc.add(new StringField("content",contentArr[i],Field.Store.YES)); doc.add(new StringField("name",nameArr[i],Field.Store.YES)); doc.add(new StringField("date","2014120"+date+“222222”,Field.Store.YES)); writer.addDocument(doc); date++; } //新的版本对 Field 进行了更改,StringField 索引但是不分词、StoreField 至存储不索引、TextField 索引并分词 } catch (CorruptIndexException e) { e.printStackTrace(); } catch (LockObtainFailedException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { if(writer!=null)writer.close(); } catch (CorruptIndexException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } } public static void main(String args[]){ IndexUtil indexUtil = new IndexUtil(); indexUtil.index(); } }
参数解释:
SetMergeFactor(合并因子),是控制 segment 合并频率的,其决定了一个索引块中包括多少个文档,当硬盘上的索引块达到这个值时,将它们合并成一个较大的索引块。当 MergeFactor 值较大时,生成索引的速度较快。MergeFactor 的默认值是 10。
SetMaxMergeDocs 最大合并文档数,默认是 Integer.MAX_VALUE。设置 segment 最大合并文档 (Document) 数值较小越有利于追加索引的速度,值较大, 越适合批量建立索引和更快的搜索。
setMaxBufferedDocs 最大缓存文档数,是控制写入一个新的 segment 前内存中保存的 document 的数目,设置较大的数目可以加快建索引速度,默认为 10。
在创建创 IndexWriter 实例的时候应注意以下几个地方:
- 尽量保持 IndexWriter 在全局中只有一个实例,因为一个 directory 中只允许一个 IndexWriter 实例访问,如果两个或者两个以上的实例同时访问一个 directory 会出现 Lock obtain timed Out 异常, 在文件夹里会出现一个 write.lock 文件。
- 在配置 LogMergePolicy 的时候不要盲目的去设置,要根据物理机器的配置来进行次测试,来达到一个理想的配置
。
查询索引
当我们创建好索引后,就可以利用 Lucene 进行索引查询,Lucene 提供了多个查询功能,下面我们进行简单介绍。
Query :一个查询的抽象类,有多个子类实现,TermQuery, BooleanQuery, PrefixQuery ,WildcardQuery 等。
Term :是搜索的基本单位,一个 Term 是由两个 String 的 field 组成。比如,Term("name",“rock”), 此时该语句是查询 name 为 rock 的条件。
IndexSearcher:当索引建立好后,用该对象进行查询。该对象只能以只读的方式打开索引,所以多个 IndexSearcher 对象可以查询一个索引目录。我们要注意一下这个现象。
在介绍几种查询方式之前,首先要初始化 directory:
directory = FSDirectory.open(new File("
C:/lucene/index02"));
其次获取 IndexSearcher:
public IndexSearcher getSearcher() { IndexReader reader = null; try { reader = DirectoryReader.open(directory); IndexSearcher searcher = new IndexSearcher(reader); return searcher; } catch (IOException e) { e.printStackTrace(); } return null; }
清单 2. 使用 TermQuery 搜索
public void searchByTerm(String field, String name, int num) { try { IndexSearcher searcher = getSearcher(); Query query = new TermQuery(new Term(field, name)); TopDocs tds = searcher.search(query, num); System.out.println("count:" + tds.totalHits); for (ScoreDoc sd : tds.scoreDocs) { Document doc = searcher.doc(sd.doc); System.out.println("docId:"+doc.get("id")); System.out.println("name:"+doc.get("name")); System.out.println("email:"+doc.get("email")); System.out.println("date:"+doc.get("date")); } } catch (CorruptIndexException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
结果:
count:1
docId:4
name:rock
email:rock@cn.ibm.com
date:2014-12-4
说明:
TermQuery 是 Lucene 查询中最基本的一种查询,它只能针对一个字段进行查询。
清单 3. 范围查询 RangeQuery (搜索指定范围的数据)
public void searchByTermRange(String field,String start,String end,int num) { try { IndexSearcher searcher = getSearcher(); BytesRef lowerTerm = new BytesRef(start); BytesRef upperTerm = new BytesRef(end); Query query = new TermRangeQuery(field,lowerTerm,upperTerm,true, true); TopDocs tds = searcher.search(query, num); System.out.println("count:"+tds.totalHits); for(ScoreDoc sd:tds.scoreDocs) { Document doc = searcher.doc(sd.doc); System.out.println("docId:"+doc.get("id")); System.out.println("name:"+doc.get("name")); System.out.println("email:"+doc.get("email")); System.out.println("date:"+doc.get("date")); } } catch (CorruptIndexException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
结果:
count:3
docId:1
name:abc
email:abc@us.ibm.com
date:20141201222222
docId:2
name:ert
email:ert@cn.ibm.com
date:20141202222222
docId:3
name:lucy
email:lucy@us.ibm.com
date:20141203222222
说明:
TermRangeQuery query=new TermRangeQuery(字段名, 起始值, 终止值, 起始值是否包含边界, 终止值是否包含边界)。
清单 4.PrefixQuery 前缀查询
//查询以 ro 开头的 name public void searchByPrefix(String field, String value, int num) { try { IndexSearcher searcher = getSearcher(); Query query = new PrefixQuery(new Term(field, value)); TopDocs tds = searcher.search(query, num); System.out.println("count:" + tds.totalHits); for (ScoreDoc sd : tds.scoreDocs) { Document doc = searcher.doc(sd.doc); System.out.println("docId:"+doc.get("id")); System.out.println("name:"+doc.get("name")); System.out.println("email:"+doc.get("email")); System.out.println("date:"+doc.get("date")); } } catch (CorruptIndexException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
结果:
count:1
docId:4
name:rock
email:rock@cn.ibm.com
date:20141204222222
说明:
前缀查询, 搜索匹配开始位置的数据类似百度的输入框。
清单 5.WildcardQuery 通配符查询
//查询 email 是 test 的 public void searchByWildcard(String field, String value, int num) { try { IndexSearcher searcher = getSearcher(); Query query = new WildcardQuery(new Term(field, value)); TopDocs tds = searcher.search(query, num); System.out.println("count" + tds.totalHits); for (ScoreDoc sd : tds.scoreDocs) { Document doc = searcher.doc(sd.doc); System.out.println("docId:"+doc.get("id")); System.out.println("name:"+doc.get("name")); System.out.println("email:"+doc.get("email")); System.out.println("date:"+doc.get("date")); } } catch (CorruptIndexException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
结果:
count1
docId:5
name:test
email:test@126.com
date:20141205222222
说明:
通配符分为两种,“*”和“?”,“*”表示任何字符,“?”表示任意一个字符。
Term term=new Term(字段名, 搜索关键字+通配符)。
清单 6.FuzzyQuery 模糊搜索
public void searchByFuzzy(int num) { try { IndexSearcher searcher = getSearcher(); FuzzyQuery query = new FuzzyQuery(new Term("name","acc"),1,1); //System.out.println(query.getPrefixLength()); TopDocs tds = searcher.search(query, num); System.out.println("count:"+tds.totalHits); for(ScoreDoc sd:tds.scoreDocs) { Document doc = searcher.doc(sd.doc); System.out.println("docId:"+doc.get("id")); System.out.println("name:"+doc.get("name")); System.out.println("email:"+doc.get("email")); System.out.println("date:"+doc.get("date")); } } catch (CorruptIndexException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
结果:
count:1
docId:1
name:abc
email:abc@us.ibm.com
date:20141201222222
说明:
FuzzyQuery(new Term("name","acc"),1,1),需要 3 个参数,第一个参数是词条对象,第二个参数是 levenshtein 算法的最小相似度,第三个参数是指与多少个前缀字符匹配。
清单 7.BooleanQuery 查询
public void searchByBoolean(int num) { try { IndexSearcher searcher = getSearcher(); BooleanQuery query = new BooleanQuery(); query.add(new TermQuery(new Term("name", "abc")),BooleanClause.Occur.SHOULD); query.add(new TermQuery(new Term("email","lucy@us.ibm.com")), BooleanClause.Occur.SHOULD); TopDocs tds = searcher.search(query, num); System.out.println("count" + tds.totalHits); for (ScoreDoc sd : tds.scoreDocs) { Document doc = searcher.doc(sd.doc); System.out.println("docId:"+doc.get("id")); System.out.println("name:"+doc.get("name")); System.out.println("email:"+doc.get("email")); System.out.println("date:"+doc.get("date")); } } catch (CorruptIndexException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
结果:
count2
docId:1
name:abc
email:abc@us.ibm.com
date:20141201222222
docId:3
name:lucy
email:lucy@us.ibm.com
date:20141203222222
说明:
BooleanQuery,也就是组合查询,允许进行逻辑 AND、OR 或 NOT 的组合,通过 BooleanQuery 的 add 方法将一个查询子句增加到某个 BooleanQuery 对象中。
BooleanClause.Occur.MUST:必须包含,相当于逻辑运算的与
BooleanClause.Occur.MUST_NOT:必须不包含,相当于逻辑运算的非
BooleanClause.Occur.SHOULD:可以包含,相当于逻辑运算的或
清单 8. 分页查询
private static void testPageSearch1(int currentPage) { int PAGE_SIZE = 10; IndexReader reader = null; try { reader = DirectoryReader.open(FSDirectory.open(new File(""))); IndexSearcher searcher = new IndexSearcher(reader); Query query = new TermQuery(new Term("name", "rock")); TopDocs topDocs = searcher.search(query, currentPage * PAGE_SIZE); ScoreDoc[] hits = topDocs.scoreDocs; int endNuM = Math.min(topDocs.totalHits, currentPage * PAGE_SIZE); for (int i = (currentPage - 1) * PAGE_SIZE; i < endNuM; i++) { Document doc = searcher.doc(hits[i].doc); System.out.print(doc.get("USERNAME")); } } catch (IOException e) { e.printStackTrace(); } } //在 Lucene 的 3.5 以后的版本,Lucene 的 API 里提供了一个分页方法 searchafter。 private ScoreDoc getLastScoreDoc(int pageIndex, int pageSize, Query query, IndexSearcher searcher) throws IOException { if (pageIndex == 1) return null; int num = pageSize * (pageIndex - 1); TopDocs tds = searcher.search(query, num); return tds.scoreDocs[num - 1]; } public void searchPageByAfter(String query, int pageIndex, int pageSize) { try { IndexSearcher searcher = getSearcher(); QueryParser parser = new QueryParser(Version.LUCENE_47, "content",new StandardAnalyzer(Version.LUCENE_47)); Query q = parser.parse(query); ScoreDoc lastSd = getLastScoreDoc(pageIndex, pageSize, q, searcher); TopDocs tds = searcher.searchAfter(lastSd, q, pageSize); for (ScoreDoc sd : tds.scoreDocs) { Document doc = searcher.doc(sd.doc); System.out.println("docId:"+doc.get("id") + ",name:" + doc.get("name")+",email:"+ doc.get("email") ); } } catch (IOException e) { e.printStackTrace(); } catch (org.apache.lucene.queryparser.classic.ParseException e) { e.printStackTrace(); } }
排序
Lucene 除了提供大量的查询功能外,还提供了一个可改变查询结果顺序的类 Sort,用户可根据自己的需求进行 Sort 排序设置。
Sort sort = new Sort(); ortField sf=new SortField("name",Type.STRING_VAL, false);
以上语句表示根据 name 进行排序,false 代表升序,如果是 true 代表降序,可以有多个 SortField,利用 Sort 的 sort.setSort(sf,sf1...) 将每个 SortField 添加到 sort 中,最后返回按 sort 进行排序的搜索结果。
TopDocs topDocs = searcher.search(query, currentPage * PAGE_SIZE,sort);
Luke 的使用
Luke 是 Lucene 搜索引擎的查看、诊断工具,如果在进程中出现搜索不到或者搜索出的结果与预期不匹配时可用 Luke 工具进行查询、修改和调试。使用 Luke 的前提是在创建索引的时候使用的是 FSDirectory。
点此进行LUKE 下载,同时,Luke 需要安装 Java 1.5 或更高版本。
下载的版本必须要与 Lucene 的版本相匹配,否则会造成索引目录打不开现象。
以下是以 lukeall-4.7.1.jar 来进行演示的结果,双击该 jar 包进行打开,如打不开,可选择以 cmd 命令行的方式进行打开,java -jar lukeall-4.7.1.jar。打开后,显示界面如下图所示:
图 1.lukeall 首页面
接下来,需要选择 index director 的路径,图 1 中红色箭头所示,然后点击 OK。详细索引信息见图 2.
图 2.Luke-Overview
上图详细的展示了 Overview 选项下的一些主要索引信息,例如 field 数量,document 的数量和 term 的数量等信息。
图 3 是 Luke 下索引的具体列表展示,可以得到每个 term 的详细信息。
图 3.Luke-Search
Luke 除了可以宏观的看到索引信息外,还提供了可视化的界面查询,查询语法是 [字段名:内容],如图 3 左上方所示。
Luke 是一个开源工具,开发人员也可以通过插件和脚本进行自定义功能定制和扩展。
结束语
本文主要介绍了如何去创建索引以及创建索引时 LogMergePolicy 的配置,合理的配置 LogMergePolicy 可以提高创建索引的效率。由于一个索引文件夹只能允许一个 IndexWriter 访问,所以最好将 IndexWriter 写成单例模式,保持全局只有一个 IndexWriter,最后一定要记得关闭 IndexWriter。. 然后依次介绍了 Lucene 里的各种 query 查询,要根据项目的实际需要选择相应的 query。还有就是要注意 Directory 的用法,API 里提供了各种 Directory,但要根据自己的实际情况选择最佳的 Directory。同时,为了减少内存开支,最好只实例化一个 Directory,因为每次打开 Directory 都需要消耗大量内存。新版本的 Lucene 提供了 SearchManager 去管理 IndexReader 和 IndexSearcher,所以不需要大家再去实现这两个对象的单例模式了。最后介绍了 Lucene 索引查询工具,该工具可以帮助开发人员快速、有效的进行索引数据的查看、添加、修改或删除。希望本文章可以为 Lucene 的学习使用人员提供一些帮助。
参考资料
学习
- Lucene 官网,了解更多 Lucene 相关内容及下载
- Luke 官网,Luke jar 包及源代码的下载
- “实战 Lucene,第 1 部分: 初识 Lucene”,熟悉 Lucene 一些基本知识及概念
- “深入 Lucene 索引机制”,深入了解 Lucene 索引机制
- developerWorks Java 技术专区:这里有数百篇关于 Java 编程各个方面的文章。
相关推荐
本课程由浅入深的介绍了Lucene4的发展历史,开发环境搭建,分析lucene4的中文分词原理,深入讲了lucenne4的系统架构,分析lucene4索引实现原理及性能优化,了解关于lucene4的搜索算法优化及利用java结合lucene4实现...
《Lucene4.X实战:构建类百度搜索的大型文档海量搜索系统》 Lucene是一个强大的全文搜索引擎库,广泛应用于各种信息检索系统。本教程通过17讲内容,深入讲解了如何利用Lucene4.X实现一个类似百度的大型文档搜索系统...
【Lucene4.X实战类baidu搜索的大型文档海量搜索系统】课程主要涵盖了Lucene搜索引擎的各个方面,包括基础和高级进阶。以下是课程的主要知识点: 1. **Lucene入门与系统架构**:介绍Lucene的基本概念,以及其系统...
在本课程中,我们主要探讨了Lucene 4.x版本的高级进阶应用,特别是针对大规模文档搜索引擎的构建。Lucene作为一个开源全文搜索引擎库,它提供了高效、灵活的索引和搜索功能,是构建高性能搜索系统的基石。在这个部分...
《Lucene4.X实战类baidu搜索的大型文档海量搜索系统》是一门全面解析Lucene搜索引擎的课程,涵盖了从基础到高级的各种应用。这门课程包括了Lucene的入门、系统架构、索引构建与优化、搜索实现以及一系列的深入实战和...
【Lucene搜索引擎】是Apache软件基金会的一个开源全文检索库,它提供了高级的索引和搜索功能,被广泛应用于各种搜索引擎和信息检索系统中。在本文中,我们将深入探讨Lucene的搜索进阶特性,包括模糊搜索、邻近搜索、...
《Lucene4.X实战类baidu搜索的大型文档海量搜索系统》系列教程涵盖了从Lucene的基础概念到高级应用的全面讲解。在这个系列中,我们特别关注了Lucene的分词器,尤其是第三部分——"Lucene分词器3"。 Lucene作为一款...
《Lucene4.X实战类百度搜索的大型文档海量搜索系统》是针对企业信息化系统中文档中心管理系统的深度应用,利用Apache Lucene构建高效、全面的全文检索功能。在这个项目实战中,我们主要关注如何处理不同格式的文档,...
《Lucene4.X实战类baidu搜索的大型文档海量搜索系统》课程主要涵盖了Lucene的基础概念、架构、索引原理及优化、搜索实现、实战应用等多个方面,旨在帮助学习者深入理解并掌握Lucene这一强大的全文搜索引擎库。...
在"Lucene4.X实战类baidu搜索的大型文档海量搜索系统"课程中,涵盖了一系列关于Lucene的核心知识点,包括入门、系统架构、索引构建与优化、搜索机制以及高级进阶等内容。 **1. Lucene入门与系统架构** Lucene的入门...
本部分主要聚焦于Lucene的查询解析器(QueryParser)及其查询语法的高级应用。 QueryParser是Lucene中用于解析用户输入的查询字符串,并将其转化为可执行的查询对象的关键工具。它允许用户使用自然语言风格的查询...
Lucene是一个开源全文检索库,由Apache软件基金会开发,它提供了文本分析、索引和搜索功能,使得开发者能够轻松地在应用程序中集成高级搜索功能。 在这个源代码中,重点是展示了如何适配Lucene 2.x的API接口,因为...
在实际应用中,除了这些基本操作,还需要了解其他Lucene组件,如Filter(用于进一步筛选结果)、Sort(用于结果排序)、分词器(如StandardAnalyzer、IKAnalyzer等)以及高级查询构造如BooleanQuery、PhraseQuery、...
《深入剖析Lucene 3.0.1:Java开源...然而,随着技术的发展,后续的版本(如Lucene 4.x及更高版本)引入了更多改进和新特性,如更强大的分布式支持和优化的内存管理,开发者应关注并适时升级以适应不断变化的技术需求。
2. **Solr 5.x**: Apache Solr是基于Lucene构建的企业级搜索平台,提供了更高级的搜索服务,如分布式搜索、实时添加文档、复杂的查询语法和结果排序等功能。Solr 5.x版本主要提升了性能,增强了稳定性,并引入了新的...
总结,Lucene 5.x的JdbcDirectory是将索引存储在数据库中的高级特性,它带来了分布式存储和数据一致性的优势,但也带来了一些挑战,如性能和成本问题。开发者需要根据具体的应用需求权衡选择,合理利用这一功能,...
《Lucene 2.9.4:开源全文检索库的深度探索》 Lucene,作为Apache软件基金会的一个顶级项目,...尽管现在有更新的版本如Lucene 8.x,但2.9.4版本的知识仍然值得我们去挖掘和掌握,因为它奠定了Lucene后续发展的基础。
通过集成Lucene.Net,开发者可以轻松地为他们的应用添加高级搜索功能,提升用户查找信息的效率。 **总结:** Lucene.Net作为.NET平台上的全文搜索引擎库,为开发者提供了强大且灵活的文本搜索解决方案。通过索引、...
Lucene 6.5.1作为该系列的一个版本,继承了Lucene 6.x系列的诸多优点,并在此基础上进行了优化与增强,以提高搜索性能和稳定性。 ### 特性与改进 #### 1. 性能提升 - **索引速度**: Lucene 6.5.1通过改进内部算法...
本篇将详细探讨Hibernate4.x的核心组件以及其在实际开发中的应用。 首先,我们要明白,Hibernate4.x的核心在于它提供了一种桥梁,将Java对象与数据库表之间建立了映射关系。通过XML或注解的方式定义实体类和数据库...