Lucene4.X 高级应用

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。

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 实例的时候应注意以下几个地方:

  1. 尽量保持 IndexWriter 在全局中只有一个实例,因为一个 directory 中只允许一个 IndexWriter 实例访问,如果两个或者两个以上的实例同时访问一个 directory 会出现 Lock obtain timed Out 异常, 在文件夹里会出现一个 write.lock 文件。
  2. 在配置 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;
	}
	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(字段名, 起始值, 终止值, 起始值是否包含边界, 终止值是否包含边界)。

//查询以 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

说明:

前缀查询, 搜索匹配开始位置的数据类似百度的输入框。

//查询 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(字段名, 搜索关键字+通配符)。

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 算法的最小相似度,第三个参数是指与多少个前缀字符匹配。

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:可以包含,相当于逻辑运算的或

	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);

posted on 2015-12-09 22:28  1130136248  阅读(201)  评论(0编辑  收藏  举报

导航