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。
回页首
首先我们介绍下如何创建索引。相关步骤分为:建立索引器 IndexWriter,建立文档对象 Document,建立信息字段对象 Field,将 Field 添加到 Document,将 Document 添加到 IndexWriter 里面,最后不要忘记关闭 IndexWriter。
package lucene; …… public class IndexUtil { private String[] idArr = {"1","2","3","4","5","6"}; private String[] emailArr = {"[email protected]","[email protected]","[email protected]", "[email protected]","[email protected]","[email protected]"}; 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
参数解释:
SetMergeFactor(合并因子),是控制 segment 合并频率的,其决定了一个索引块中包括多少个文档,当硬盘上的索引块达到这个值时,将它们合并成一个较大的索引块。当 MergeFactor 值较大时,生成索引的速度较快。MergeFactor 的默认值是 10。
SetMaxMergeDocs 最大合并文档数,默认是 Integer.MAX_VALUE。设置 segment 最大合并文档 (Document) 数值较小越有利于追加索引的速度,值较大, 越适合批量建立索引和更快的搜索。
setMaxBufferedDocs 最大缓存文档数,是控制写入一个新的 segment 前内存中保存的 document 的数目,设置较大的数目可以加快建索引速度,默认为 10。
在创建创 IndexWriter 实例的时候应注意以下几个地方:
。
当我们创建好索引后,就可以利用 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;
}
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:[email protected]
date:2014-12-4
说明:
TermQuery 是 Lucene 查询中最基本的一种查询,它只能针对一个字段进行查询。
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:[email protected]
date:20141201222222
docId:2
name:ert
email:[email protected]
date:20141202222222
docId:3
name:lucy
email:[email protected]
date:20141203222222
说明:
TermRangeQuery query=new TermRangeQuery(字段名, 起始值, 终止值, 起始值是否包含边界, 终止值是否包含边界)。
//查询以 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:[email protected]
date:20141204222222
说明:
前缀查询, 搜索匹配开始位置的数据类似百度的输入框。
//查询 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:[email protected]
date:20141205222222
说明:
通配符分为两种,“*”和“?”,“*”表示任何字符,“?”表示任意一个字符。
Term term=new Term(字段名, 搜索关键字+通配符)。
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:[email protected]
date:20141201222222
说明:
FuzzyQuery(new Term("name","acc"),1,1),需要 3 个参数,第一个参数是词条对象,第二个参数是 levenshtein 算法的最小相似度,第三个参数是指与多少个前缀字符匹配。
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","[email protected]")), 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:[email protected]
date:20141201222222
docId:3
name:lucy
email:[email protected]
date:20141203222222
说明:
BooleanQuery,也就是组合查询,允许进行逻辑 AND、OR 或 NOT 的组合,通过 BooleanQuery 的 add 方法将一个查询子句增加到某个 BooleanQuery 对象中。
BooleanClause.Occur.MUST:必须包含,相当于逻辑运算的与
BooleanClause.Occur.MUST_NOT:必须不包含,相当于逻辑运算的非
BooleanClause.Occur.SHOULD:可以包含,相当于逻辑运算的或
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);