针对lucene全文检索引擎写一下多种查询检索的方式
这里采用的是英文分词,单独写了一篇关于中文分词与高亮显示,如下:
lucene全文检索之 中文分词 以及 高亮显示,点击进入
lucene官网 http://lucene.apache.org这里写链接内容
首先需要引入lucene的maven依赖,如下:
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<version>5.3.1</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-queryparser</artifactId>
<version>5.3.1</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-common</artifactId>
<version>5.3.1</version>
</dependency>
下面我就直接贴代码吧,如下:
package luceneDemo;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.IntField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.search.TermRangeQuery;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.search.NumericRangeQuery;
import org.apache.lucene.search.PrefixQuery;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
/**
* 全文检索案例
* @author mszhou
*/
public class luceneDemo {
/**
* 演示将数据写入索引文件中
* 在项目中我们会定时将数据写入索引文件中,
* 然后在索引文件中进行搜索,这样比在数据库中搜索效率高的多,
* 而且检索远比数据库的模糊匹配强大的多,这也是检索工具牛逼之处
*
* 需要注意的是,如果在项目中定时写入索引,
* 需要判断下该数据是否存在,存在则修改,不存在再进行新增
*
* @param luceneDir 该参数表示索引所存放的目录
*/
public static void writeIndex(String luceneDir){
IndexWriter writer = null;//声明创建索引写入器
try{
//创建索引目录 , 这里参数luceneDir 表示索引存放目录
Directory dir=FSDirectory.open(Paths.get(luceneDir));
//创建标准分词器(一般英文使用这个,不适合中文使用)
Analyzer analyzer=new StandardAnalyzer(); // 标准分词器
IndexWriterConfig iwc=new IndexWriterConfig(analyzer);
//创建索引写入器实例,通过IndexWriterConfig设置相关配置
writer=new IndexWriter(dir, iwc);
//=== 这里造一些数据用作演示,也可以直接读取数据库中的数据 开始========
List <Map<String,String>> list=new ArrayList<Map<String,String>>();
//假数据1
Map<String,String> map1 =new HashMap<String, String>();
map1.put("title", "weejij dasa ");//表示标题字段
map1.put("centent", "hdaiads sd s g r44e r hgg f hd 4 ");//表示标题字段
list.add(map1);
//假数据2
Map<String,String> map2 =new HashMap<String, String>();
map2.put("title", "demo12 i33s ss");//表示标题字段
map2.put("centent", "hdaiads sd s g r44e r hgg f hd 4 ");//表示标题字段
list.add(map2);
//假数据1
Map<String,String> map3 =new HashMap<String, String>();
map3.put("title", "demo 12i131s ss");//表示标题字段
map3.put("centent", "hdaiads sd s g r44e r hgg f hd 4 ");//表示标题字段
list.add(map3);
//=== 这里造一些数据用作演示,也可以直接读取数据库中的数据 结束========
//遍历数据,并且将数据写入到索引中
for(int i=0 ;i<list.size();i++){
// ====== 获取数据开始 ======
Map<String,String> mapCen=list.get(i);
String title = mapCen.get("title").toString();//获取标题
String centent = mapCen.get("centent").toString();//获取内容
// ====== 获取数据结束 ======
//========= 将数据写入索引开始 =================
//创建Document对象,Document表示lucene索引的数据结构单元
Document doc=new Document();
//添加id 数据,最后面Field.Store.YES表示存储该字段
doc.add(new IntField("id", i, Field.Store.YES));
/*添加标题数据,最后面Field.Store.YES 表示存储该字段
*注意:如果需要检索该字段,建议使用TextField类型,并且设置为 Field.Store.YES
*/
doc.add(new TextField("title",title , Field.Store.YES));
//添加标题内容数据,最后面Field.Store.NO 表示不存储该字段
doc.add(new TextField("centent", centent, Field.Store.NO));
// 写入索引
writer.addDocument(doc);
//========= 将数据写入索引结束 =================
}
}catch(Exception e){
System.out.println("发生了异常!");
e.printStackTrace();
}finally{
try {
writer.close();//关闭流,一定要记得关闭
} catch (IOException e) {
}
}
}
/**
* 演示对特定项搜索
* @param luceneDir 该参数表示索引所存放的目录
* @param field 表示需要搜索的项(就是写入时属性字段名称)
* @param qu 需要检索的关键词
*/
public static void testTermQuery(String luceneDir,String field, String qu){
IndexReader reader = null;//声明索引读取器
try{
//创建索引目录 , 这里参数luceneDir 表示索引存放目录
Directory dir=FSDirectory.open(Paths.get(luceneDir));
//创建索引读取器
reader=DirectoryReader.open(dir);
//创建索引搜索器
IndexSearcher is=new IndexSearcher(reader);
//创建查询器,参数field表示属性字段,qu表示检索的关键词
Term t=new Term(field,qu);
Query query=new TermQuery(t);
//这里查询前10条匹配的数据,第二个参数为10表示查前十条
TopDocs hits=is.search(query, 10);
//打印
System.out.println("检索文档字段"+ field +" ,匹配 "+qu+" , 查询到"+hits.totalHits+"个记录");
//遍历查询到的数据
for(ScoreDoc scoreDoc:hits.scoreDocs){
Document doc=is.doc(scoreDoc.doc);
String id = doc.get("id")==null?"":doc.get("id").toString();//获取id
String title = doc.get("title")==null?"":doc.get("title").toString();//获取标题
//由于写入的时候 ,设置了Field.Store.NO 表示不存储该字段,所以这里读取不到
//String centent = doc.get("centent")==null"":doc.toString();//获取内容
//打印数据
System.out.println("id是:"+id+" ,标题是:"+title);
}
}catch(Exception e){
System.out.println("发生了异常!");
e.printStackTrace();
}finally{
try {
reader.close();//关闭流,一定要记得关闭
} catch (IOException e) {
}
}
}
/**
* 演示解析查询表达式(这里演示查询title字段属性)
* @param luceneDir 该参数表示索引所存放的目录
* @param titleStr 需要检索的关键词
*/
public static void testQueryParser(String luceneDir,String titleStr){
IndexReader reader = null;//声明索引读取器
try{
//创建索引目录 , 这里参数luceneDir 表示索引存放目录
Directory dir=FSDirectory.open(Paths.get(luceneDir));
//创建索引读取器
reader=DirectoryReader.open(dir);
//创建索引搜索器
IndexSearcher is=new IndexSearcher(reader);
//创建标准分词器(一般英文使用这个,不适合中文使用)
Analyzer analyzer=new StandardAnalyzer(); // 标准分词器
//创建QueryParser查询器,查询title属性字段
QueryParser parser=new QueryParser("title", analyzer);
//设置查询关键词
Query query=parser.parse(titleStr);
//这里查询前10条匹配的数据,第二个参数为10表示查前十条
TopDocs hits=is.search(query, 10);
//打印信息
System.out.println("匹配 "+titleStr+" , 查询到"+hits.totalHits+"个记录");
//遍历查询到的数据
for(ScoreDoc scoreDoc:hits.scoreDocs){
Document doc=is.doc(scoreDoc.doc);
String id = doc.get("id")==null?"":doc.get("id").toString();//获取id
String title = doc.get("title")==null?"":doc.get("title").toString();//获取标题
//由于写入的时候 ,设置了Field.Store.NO 表示不存储该字段,所以这里读取不到
//String centent = doc.get("centent").toString();//获取内容
//打印数据
System.out.println("id是:"+id+" ,标题是:"+title);
}
}catch(Exception e){
System.out.println("发生了异常!");
e.printStackTrace();
}finally{
try {
reader.close();//关闭流,一定要记得关闭
} catch (IOException e) {
}
}
}
/**
* 演示指定项范围搜索
* @param luceneDir 该参数表示索引所存放的目录
*/
public static void testTermRangeQuery(String luceneDir){
IndexReader reader = null;//声明索引读取器
try{
//创建索引目录 , 这里参数luceneDir 表示索引存放目录
Directory dir=FSDirectory.open(Paths.get(luceneDir));
//创建索引读取器
reader=DirectoryReader.open(dir);
//创建索引搜索器
IndexSearcher is=new IndexSearcher(reader);
/*
* 创建TermRangeQuery查询器,查询title属性字段 从a到e范围内
* 一般对于英文来说,进行ASC码范围查询还有那么一点意义,
* 中文汉字进行ASC码值比较没什么太大意义,
* 一般数字范围查询NumericRangeQuery用的比较多一点,
* 比如价格,年龄,金额,数量等等都涉及到数字,数字范围查询需求也很普遍
*
* 后面两个boolean值用来控制是否包含两个上下边界值的。
*/
TermRangeQuery query=new TermRangeQuery("title", new BytesRef("a".getBytes()), new BytesRef("e".getBytes()), true, true);
//这里查询前10条匹配的数据,第二个参数为10表示查前十条
TopDocs hits=is.search(query, 10);
//遍历查询到的数据
for(ScoreDoc scoreDoc:hits.scoreDocs){
Document doc=is.doc(scoreDoc.doc);
String id = doc.get("id")==null?"":doc.get("id").toString();//获取id
String title = doc.get("title")==null?"":doc.get("title").toString();//获取标题
//打印数据
System.out.println("id是:"+id+" ,标题是:"+title);
}
}catch(Exception e){
System.out.println("发生了异常!");
e.printStackTrace();
}finally{
try {
reader.close();//关闭流,一定要记得关闭
} catch (IOException e) {
}
}
}
/**
* 演示指定数字范围
* @param luceneDir 该参数表示索引所存放的目录
*/
public static void testNumericRangeQuery(String luceneDir){
IndexReader reader = null;//声明索引读取器
try{
//创建索引目录 , 这里参数luceneDir 表示索引存放目录
Directory dir=FSDirectory.open(Paths.get(luceneDir));
//创建索引读取器
reader=DirectoryReader.open(dir);
//创建索引搜索器
IndexSearcher is=new IndexSearcher(reader);
/*
* 创建NumericRangeQuery查询器,查询id属性字段 从1到2范围内
*
* 后面两个boolean值用来控制是否包含两个上下边界值的。
*/
NumericRangeQuery<Integer> query=NumericRangeQuery.newIntRange("id", 1, 2, true, true);
//这里查询前10条匹配的数据,第二个参数为10表示查前十条
TopDocs hits=is.search(query, 10);
//遍历查询到的数据
for(ScoreDoc scoreDoc:hits.scoreDocs){
Document doc=is.doc(scoreDoc.doc);
String id = doc.get("id")==null?"":doc.get("id").toString();//获取id
String title = doc.get("title")==null?"":doc.get("title").toString();//获取标题
//打印数据
System.out.println("id是:"+id+" ,标题是:"+title);
}
}catch(Exception e){
System.out.println("发生了异常!");
e.printStackTrace();
}finally{
try {
reader.close();//关闭流,一定要记得关闭
} catch (IOException e) {
}
}
}
/**
* 演示指定字符串开头搜索
* @param luceneDir 该参数表示索引所存放的目录
*/
public static void testPrefixQuery(String luceneDir){
IndexReader reader = null;//声明索引读取器
try{
//创建索引目录 , 这里参数luceneDir 表示索引存放目录
Directory dir=FSDirectory.open(Paths.get(luceneDir));
//创建索引读取器
reader=DirectoryReader.open(dir);
//创建索引搜索器
IndexSearcher is=new IndexSearcher(reader);
/*
* 创建NumericRangeQuery查询器,
* 查询title属性字段分词索引以 w 开头的数据
*/
PrefixQuery query=new PrefixQuery(new Term("title","w"));
//这里查询前10条匹配的数据,第二个参数为10表示查前十条
TopDocs hits=is.search(query, 10);
//遍历查询到的数据
for(ScoreDoc scoreDoc:hits.scoreDocs){
Document doc=is.doc(scoreDoc.doc);
String id = doc.get("id")==null?"":doc.get("id").toString();//获取id
String title = doc.get("title")==null?"":doc.get("title").toString();//获取标题
//打印数据
System.out.println("id是:"+id+" ,标题是:"+title);
}
}catch(Exception e){
System.out.println("发生了异常!");
e.printStackTrace();
}finally{
try {
reader.close();//关闭流,一定要记得关闭
} catch (IOException e) {
}
}
}
/**
* 演示多条件查询
* @param luceneDir 该参数表示索引所存放的目录
*/
public static void testBooleanQuery(String luceneDir){
IndexReader reader = null;//声明索引读取器
try{
//创建索引目录 , 这里参数luceneDir 表示索引存放目录
Directory dir=FSDirectory.open(Paths.get(luceneDir));
//创建索引读取器
reader=DirectoryReader.open(dir);
//创建索引搜索器
IndexSearcher is=new IndexSearcher(reader);
//========== 创建多个查询条件 开始===============
BooleanQuery.Builder booleanQuery=new BooleanQuery.Builder();
/*
* 条件1,
* 创建NumericRangeQuery查询器,查询id属性字段 从1到2范围内
* 后面两个boolean值用来控制是否包含两个上下边界值的。
*/
NumericRangeQuery<Integer> query1=NumericRangeQuery.newIntRange("id", 1, 2, true, true);
/*
* 添加查询条件1 到组合查询器中
* BooleanClause.Occur.MUST 表示与 (相当于并且)
* BooleanClause.Occur.SHOULD 表示或 (相当于或者)
* BooleanClause.Occur.MUST_NOT 表示不包含 (相当于not)
*/
booleanQuery.add(query1,BooleanClause.Occur.MUST);
/*
* 条件2,
* 创建NumericRangeQuery查询器,
* 查询title属性字段分词索引以 12 开头的数据
*/
PrefixQuery query2=new PrefixQuery(new Term("title","12"));
/*
* 添加查询条件2 到组合查询器中
* BooleanClause.Occur.MUST 表示与 (相当于并且)
* BooleanClause.Occur.SHOULD 表示或 (相当于或者)
* BooleanClause.Occur.MUST_NOT 表示不包含 (相当于not)
*/
booleanQuery.add(query2,BooleanClause.Occur.MUST);
//========== 创建多个查询条件 结束===============
/*
* 这里查询前10条匹配的数据,第二个参数为10表示查前十条
* 这里查询检索表示满足条件1并且同时满足条件2的数据
*/
TopDocs hits=is.search(booleanQuery.build(), 10);
//遍历查询到的数据
for(ScoreDoc scoreDoc:hits.scoreDocs){
Document doc=is.doc(scoreDoc.doc);
String id = doc.get("id")==null?"":doc.get("id").toString();//获取id
String title = doc.get("title")==null?"":doc.get("title").toString();//获取标题
//打印数据
System.out.println("id是:"+id+" ,标题是:"+title);
}
}catch(Exception e){
System.out.println("发生了异常!");
e.printStackTrace();
}finally{
try {
reader.close();//关闭流,一定要记得关闭
} catch (IOException e) {
}
}
}
/**
* 测试上面的方法
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
//表示索引目录
String luceneDir = "D://lucene";
//测试该方法写入数据到索引文件中
//writeIndex(luceneDir);
//测试检索属性字段title,检索关键词为demo
//testTermQuery(luceneDir,"title", "demo");
//测试该方法检索询
//testQueryParser(luceneDir,"demo");
//测试指定项范围搜索方法
//testTermRangeQuery(luceneDir);
//测试指定数字范围搜索方法
//testNumericRangeQuery(luceneDir);
//测试指定字符串开头搜索方法
//testPrefixQuery(luceneDir);
//测试多条件查询搜索方法
testBooleanQuery(luceneDir);
}
}