下面的一个模式是我们经常使用的。
相对于索引的创建而言,索引的搜索是使用频繁的。所以 IndexReader 是会经常使用的,所以我们很自然地想到应该将 IndexReader 设计成一个单例模式。但是索引增加、修改、删除以后,IndexReader 须要重新读取索引信息,才能保证我们的索引信息是准确的,那有没有办法不用重新打开索引,就能保证我们的 IndexReader 是读取最新的索引呢?
有的 , 使用 DirectoryReader 类的静态方法 openIfChanged 就可以达到目的,这个判断会先判断索引是否变更,如果变更,我们要先把原来的 IndexReader 释放。下面的例子展示了 IndexReader 的使用过程。
/**
* 重用一些旧的 IndexReader
* @return
*/
public IndexSearcher getSearcher() {
try {
if(reader==null) {
reader = DirectoryReader.open(directory);
} else {
// 如果 IndexReader 不为空,就使用 DirectoryReader 打开一个索引变更过的 IndexReader 类
// 此时要记得把旧的索引对象关闭
// 参考资料:Lucene系列-近实时搜索(1)
// http://blog.csdn.net/whuqin/article/details/42922813
IndexReader tr = DirectoryReader.openIfChanged((DirectoryReader)reader);
if(tr!=null) {
reader.close();
reader = tr;
}
}
return new IndexSearcher(reader);
} catch (CorruptIndexException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
下面归纳了一些常用的搜索方法,最后给出了整个搜索的测试例子,供大家参考。
1、TermQuery 搜索特定的项(上一节已经介绍过)
// 搜索特定的项
Query query = new TermQuery(new Term(field,value));
2、TermRangeQuery 搜索特定范围的项
这个 Query 不适用于数字范围查询,数字范围查询请使用 NumericRangeQuery 代替
Query query = new TermRangeQuery(field,new BytesRef(start.getBytes()),new BytesRef(end.getBytes()),true,true);
3、NumericRangeQuery 搜索数字范围的项
NumericRangeQuery<Integer> query = NumericRangeQuery.newIntRange(field,start,end,true,true);
4、PrefixQuery 前缀匹配搜索
Query query = new PrefixQuery(new Term(field,value));
5、WildcardQuery 通配符搜索
Query query = new WildcardQuery(new Term(field,value));
6、FuzzyQuery 模糊匹配搜索
模糊匹配的意思是:搜索的关键字即使有错,在一定范围内都可以被搜索到
FuzzyQuery query = new FuzzyQuery(new Term(field,value),maxEdits,prefixLength);
7、BooleanQuery 多个条件的查询
BooleanQuery.Builder booleanQuery = new BooleanQuery.Builder();
Query query1 = new TermQuery(new Term(field1,value1));
Query query2 = new TermQuery(new Term(field2,value2));
booleanQuery.add(query1,BooleanClause.Occur.MUST);
booleanQuery.add(query2,BooleanClause.Occur.MUST);
8、PhraseQuery 短语查询
PhraseQuery phraseQuery = new PhraseQuery();
phraseQuery.setSlop(slop);
phraseQuery.add(new Term(field,value1));
phraseQuery.add(new Term(field,value2));
9、QueryParser 方式的查询
功能最最强大,几乎涵盖上上面几种方式的查询。
Analyzer analyzer = new SimpleAnalyzer();
// QueryParser 构造器的第 1 个参数表示默认的搜索域
// 实例化 QueryParser 的时候,需要指定一个分词器(构造函数的第 2 个参数)
// 【重要】这个分析器不一定要和索引的时候使用的分析器相同
QueryParser queryParser = new QueryParser(filedName,analyzer);
// 开启第一个字符的通配符匹配,默认关闭因为效率不高
// queryParser.setAllowLeadingWildcard(true);
// 改变空格的默认操作符,以下可以改成AND
// parser.setDefaultOperator(Operator.AND);
Query query = queryParser.parse(el);
QueryParser 构造函数的第 1 个字段表示默认的搜索域。
其中 el 表示查询表达式,查询表达式的内容非常丰富,我们要通过查询表达式来完成复杂的查询工作。
例如:
(1)“- name:mike + like” 表示 “匹配 name 中没有 mike 但是 content 中必须有 like 的, + 和 - 要放置到域说明前面”;
(2) “\”I like football\”” 表示完全匹配 I like football 。
关于查询表达式更详细的内容,可以参考《Lucene 实战》。
下面展示出了一整个搜索工具类供大家参考如何使用:
public class SearcherUtil {
private String[] ids = {"1", "2", "3", "4", "5", "6"};
private String[] names = {"liwei", "zhouguang", "liaoqunying", "yuanlian", "wudi", "huzhenyu"};
private String[] emails = {"[email protected]", "[email protected]", "[email protected]", "[email protected]", "[email protected]", "[email protected]"};
private String[] contents = {
"I enjoy a folk song",
"I come from Shanghai jiaotong university",
"I am a university professor",
"I am very cool",
"I like football and I like basketball too",
"I am a operations engineer"
};
// 用于测试创建日期数据索引
private Date[] dates = null;
// 用于测试创建数字索引
private int[] attachs = {4, 0, 17, 4, 7, 3};
private Directory directory;
private IndexReader indexReader;
private String indexDir = "C:\\dev\\lucene";
private Map scores = new HashMap();
/**
* 设置日期类型的数据
*/
private void setDates() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
try {
dates = new Date[ids.length];
dates[0] = sdf.parse("1987-07-06");
dates[1] = sdf.parse("1990-03-20");
dates[2] = sdf.parse("1989-01-06");
dates[3] = sdf.parse("1993-03-17");
dates[4] = sdf.parse("1974-07-27");
dates[5] = sdf.parse("1987-05-07");
} catch (ParseException e) {
e.printStackTrace();
}
}
public SearcherUtil(){
setDates();
scores.put("163.com",2.0f);
scores.put("qq.com", 1.5f);
try {
directory = FSDirectory.open(Paths.get(indexDir));
} catch (IOException e) {
e.printStackTrace();
}
}
private IndexWriter getIndexWriter(){
IndexWriter indexWriter = null;
Analyzer analyzer = new SimpleAnalyzer();
IndexWriterConfig iwc = new IndexWriterConfig(analyzer);
try {
indexWriter= new IndexWriter(directory,iwc);
}catch (IOException e){
e.printStackTrace();
}
return indexWriter;
}
/**
* 创建索引
*/
public void index() {
IndexWriter writer = null;
try {
writer = getIndexWriter();
writer.deleteAll();
Document doc = null;
for(int i=0;inew Document();
doc.add(new StringField("id",ids[i], Field.Store.YES));
StringField emailField = new StringField("email",emails[i],Field.Store.YES);
String et = emails[i].substring(emails[i].lastIndexOf("@")+1);
// System.out.println("email 的后缀 => " + et);
// 目前还不清楚如何使用加权
/*if(scores.containsKey(et)) {
emailField.setBoost(scores.get(et));
} else {
emailField.setBoost(1.0f);
}*/
doc.add(emailField);
doc.add(new TextField("content",contents[i],Field.Store.NO));
doc.add(new StringField("name",names[i],Field.Store.YES));
// 参考资料:一步一步跟我学习lucene(2)---lucene的各种Field及其排序
// http://blog.csdn.net/wuyinggui10000/article/details/45538155
//存储数字的 Field
doc.add(new IntField("attach",attachs[i], Field.Store.YES));
//存储日期的 Field
doc.add(new LongField("date",dates[i].getTime(), Field.Store.YES));
writer.addDocument(doc);
}
} 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();
}
}
}
/**
* 获得 IndexSearcher
* 因为操作 IndexReader 是一种消耗比较大的操作,因此我们要将 IndexReader 设计成单例
* 但是我们又不希望索引的更改导致我们要重新读取索引
* 这是一种标准的写法,要记录下来
* @return
*/
public IndexSearcher getIndexSearcher(){
try {
if(indexReader==null){
indexReader = DirectoryReader.open(directory);
}else {
IndexReader newReader = DirectoryReader.openIfChanged((DirectoryReader) indexReader);
if(newReader!=null){
// 要记得将原来的 IndexReader 对象关掉
indexReader.close();
indexReader = newReader;
}
}
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
return indexSearcher;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* 与上面的方法重载,传入一个 Directory 对象
* @param directory
* @return
*/
public IndexSearcher getIndexSearcher(Directory directory){
try {
if(indexReader == null){
indexReader = DirectoryReader.open(directory);
}else {
IndexReader newReader = DirectoryReader.openIfChanged((DirectoryReader)indexReader);
indexReader.close();
indexReader = newReader;
}
return new IndexSearcher(indexReader);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public void searchByTerm(String field,String value,int num){
// 首先获得 IndexSearcher
IndexSearcher searcher = getIndexSearcher();
// 搜索特定的项
Query query = new TermQuery(new Term(field,value));
try {
TopDocs topDocs = searcher.search(query,num);
System.out.println("实际搜索到的记录数 => " + topDocs.totalHits);
Document document = null;
for(ScoreDoc scoreDoc:topDocs.scoreDocs){
document = searcher.doc(scoreDoc.doc);
String result = "name => " + document.get("name") + "\t email => "+ document.get("email") +
"\t id => " + document.get("id") + "\t attach => " + document.get("attach") + "\t date => " + document.get("date");
System.out.println(result);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
// IndexSearcher 都不用关闭了
IndexReader reader = searcher.getIndexReader();
System.out.println("测试 reader 是否一样 => " + (reader == indexReader) );
}
}
/**
*
* @param field
* @param start
* @param end
* @param num
*/
public void searchByTermRange(String field,String start,String end,int num){
IndexSearcher searcher = getIndexSearcher();
/**
* 这个 Query 不适用于数字范围查询,数字范围查询请使用 NumericRangeQuery 代替
*/
Query query = new TermRangeQuery(field,new BytesRef(start.getBytes()),new BytesRef(end.getBytes()),true,true);
showQueryResult(searcher,query,num);
}
/**
*
* @param field
* @param start
* @param end
* @param num
*/
public void searchByNumericRangeQuery(String field,Integer start,Integer end,int num){
IndexSearcher searcher = getIndexSearcher();
NumericRangeQuery query = NumericRangeQuery.newIntRange(field,start,end,true,true);
showQueryResult(searcher,query,num);
}
/**
*
* @param searcher
* @param query
* @param num
*/
private void showQueryResult(IndexSearcher searcher,Query query,Integer num){
TopDocs topDocs = null;
try {
topDocs = searcher.search(query,num);
System.out.println("实际搜索到的记录数 => " + topDocs.totalHits);
Document document = null;
for(ScoreDoc scoreDoc:topDocs.scoreDocs){
document = searcher.doc(scoreDoc.doc);
String result = "name => " + document.get("name") + "\t email => "+ document.get("email") +
"\t id => " + document.get("id") + "\t attach => " + document.get("attach") + "\t date => " + document.get("date");
System.out.println(result);
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 前缀匹配查询
* @param field
* @param value
* @param num
*/
public void searchByPrefix(String field,String value,int num){
IndexSearcher searcher = getIndexSearcher();
Query query = new PrefixQuery(new Term(field,value));
showQueryResult(searcher,query,num);
}
/**
* 通配符查询
* 通配符: * 表示匹配任意多个字符,? 表示匹配一个字符
* @param field
* @param value
* @param num
*/
public void searchByWildcard(String field,String value,int num){
IndexSearcher searcher = getIndexSearcher();
Query query = new WildcardQuery(new Term(field,value));
showQueryResult(searcher,query,num);
}
/**
* 多个条件的查询
* MUST 表示必须要有,即“且,交集”
* SHOULD 表示可以有,也可以没有,即“或者,并集”
* @param field1
* @param value1
* @param field2
* @param value2
* @param num
*/
public void searchByBoolean(String field1,String value1,String field2,String value2, int num){
IndexSearcher searcher = getIndexSearcher();
BooleanQuery.Builder booleanQuery = new BooleanQuery.Builder();
Query query1 = new TermQuery(new Term(field1,value1));
Query query2 = new TermQuery(new Term(field2,value2));
booleanQuery.add(query1,BooleanClause.Occur.MUST);
booleanQuery.add(query2,BooleanClause.Occur.MUST);
showQueryResult(searcher,booleanQuery.build(),num);
}
/**
*
* slop 表示一个半径,正着走,反着走都是可以查询到的
* 但是要主要搜索的关键字必须是小写
*
* 短语查询,仅仅针对英文有效,中文并不支持
* @param field
* @param value1
* @param value2
* @param num
*/
public void searchByPrase(String field,String value1,String value2,int slop,int num){
IndexSearcher searcher = getIndexSearcher();
PhraseQuery phraseQuery = new PhraseQuery();
phraseQuery.setSlop(slop);
phraseQuery.add(new Term(field,value1));
//第一个Term
phraseQuery.add(new Term(field,value2));
showQueryResult(searcher,phraseQuery,num);
}
/**
* 模糊查询
* @param field
* @param value
* @param num
*/
public void searchByFuzzy(String field,String value,int maxEdits, int prefixLength,int num){
IndexSearcher searcher = getIndexSearcher();
FuzzyQuery query = new FuzzyQuery(new Term(field,value),maxEdits,prefixLength);
showQueryResult(searcher,query,num);
}
/**
* 根据一个字符串,实现了上述各种特殊的查询功能
* 那就要通过 QueryParser 来完成
* @param query
* @param num
*/
public void searchByQueryParser(Query query,int num){
IndexSearcher searcher = getIndexSearcher();
showQueryResult(searcher,query,num);
}
}