Lucene整理--索引的搜索

看lucene主页(http://lucene.apache.org/)上目前lucene已经到4.9.0版本了, 参考学习的书是按照2.1版本讲解的,写的代码例子是用的3.0.2版本的,版本

的不同导致有些方法的使用差异,但是大体还是相同的。

源代码用到的jar包(3.0.2版本)下载地址

参考资料:

1、公司内部培训资料

2、《Lucene搜索引擎开发权威经典》于天恩著.

Lucene使用挺简单的,耐心看完都能学会,还有源代码。


索引的创建一文中介绍了创建Lucene索引的基本方法。索引建好以后,就可以执行搜索了。执行搜索的过程就是:将用户输入的关键字进行处理,从而得到搜索结果。

使用Lucene执行搜索,首先要创建IndexSearcher对象,然后通过Term和Query对象来封装用户输入的搜索条件,最后将结果封装在Hits对象中,返回给用户。

本文主要要介绍Query对象的几个子类给我们提供的不同的搜索方法。

indexSearcher.search(Query q):这个方法是对索引进行检索,是整个检索系统的核心。  其中q是Query对象的子类

首先为下面几个实例提前创建一个索引:

import java.io.File;
import java.io.IOException;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.SimpleFSDirectory;
import org.apache.lucene.util.Version;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
/**
 * 创建一个索引
 * 
 * @author Administrator
 * 
 */
public class CreateLuceneIndex {
	private static String Brazil = "唯一参加过此前全部19届世界杯决赛圈的球队,世界足坛真正的王者,"
			+ "5次夺得世界杯桂冠。上次夺魁是2002年的韩日世界杯,由3R(罗纳尔多、罗纳尔迪尼奥、里瓦尔多)"
			+ "领衔的桑巴舞者倾倒东亚乃至整个世界,以7战全胜的战绩轻松登顶。上届世界杯,前队长邓加执教"
			+ "的巴西八强战1-2不敌荷兰。本届世界杯作为东道主的他们自动晋级决赛圈。该队主帅恰好是率队夺"
			+ "得韩日世界杯桂冠的斯科拉里,去年年底他临危受命,代替梅内塞斯,再度执教桑巴军团。球队头牌"
			+ "是今夏刚刚登陆巴萨的内马尔,蒂亚戈-席尔瓦、阿尔维斯、奥斯卡、胡尔克等名将在阵。世界杯最好战绩"
			+ ":冠军(1958、1962、1970、1994、2002)";
	private static String Argentina = "此前15次参赛,4次进入决赛,在1978年及1986年两次夺冠。上届南非世界杯,"
			+ "他们在八强战耻辱性地被德国4球横扫。本次世预赛,阿根廷取得9胜5平2负的战绩,以32分拿到南美区头名。"
			+ "该队主帅是2011年接过教鞭的萨维利亚,曾经常年担任帕萨雷拉助教的他2009年才开启主帅生涯,率大学生夺"
			+ "得解放者杯是其巅峰。探戈军团的王牌无疑是梅西,完成过金球奖四连庄的他要想成为真正意义上的球王,必须"
			+ "率队拿下世界杯桂冠,阿奎罗、伊瓜因、马斯切拉诺等名将将是梅西最可倚重的助手。"
			+ "世界杯最好战绩:冠军(1978、1986)";
	private static String Holland = "此前9次参赛,3次杀进决赛圈,但均屈居亚军。上届南非世界杯,他们只是加时赛惜"
			+ "败西班牙。此次世预赛,橙军与罗马尼亚、匈牙利、土耳其、爱沙尼亚以及安道尔同组,取得9胜1平的战绩,是"
			+ "欧洲区首支晋级决赛阶段的球队。该队主帅是二进宫的前巴萨及拜仁名帅范加尔,他也是让橙军迅速走出去年欧洲"
			+ "杯惨败低谷的关键人物。队内拥有范佩西、罗本、斯内德、范德法特等中前场名将,若能做好防守,橙军有望在巴"
			+ "西再创佳绩。世界杯最好战绩:亚军(1974、1978、2010)";
	private static String Spain = "此前13次出战世界杯,1950年获得殿军,上届淘汰赛连克葡萄牙、巴拉圭、德国以及荷兰,"
			+ "最终捧得世界杯桂冠,也成为首支在非欧洲大陆夺冠的欧洲球队。巴西世界杯预选赛,西班牙与法国、芬兰、格鲁"
			+ "吉亚及白俄罗斯同组,取得6胜2平的不败战绩,以小组头名晋级。该队由冠军教头博斯克坐镇,后场拥有卡西利亚"
			+ "斯、皮克、拉莫斯等强将,中场有哈维、伊涅斯塔、布斯克茨、阿隆索等世界级球员,前场也有席尔瓦、马塔、迭"
			+ "戈.科斯塔、托雷斯、略伦特等大牌。今年的联合会杯,他们决赛0-3不敌东道主巴西。世界杯最好战绩:冠军(2010)";
	private static String France = "此前13次参赛,最好成绩是1998年在本土举办的世界杯上折桂,其中决赛3-0轻取巴西。此次"
			+ "世预赛,他们与西班牙、格鲁吉亚、芬兰以及白俄罗斯同组,取得5胜2平1负的战绩,以小组第二身份出战附加赛,总"
			+ "比分3-2惊险逆转乌克兰晋级。该队主帅是率队捧得世界杯的前队长德尚。阵中头牌无疑是今年的欧洲最佳球员及金球奖"
			+ "大热里贝里,科斯切尔尼、吉鲁、本泽马等球员也声名赫赫。世界杯最好战绩:冠军(1998)";

	private static String Italy = "此前19届世界杯仅两次缺席决赛阶段,4次夺冠的成绩仅次于巴西,2006年在现任恒大主帅里皮的"
			+ "率领下登顶,但上届却折戟小组赛。此次世预赛,意大利与保加利亚、马耳他、亚美尼亚、丹麦以及捷克同组,以6胜4平"
			+ "的不败战绩拿到小组头名。该队主帅是前佛罗伦萨名帅普兰德利,2010年世界杯后接过教鞭。布冯、德罗西以及皮尔洛仍"
			+ "是队中砥柱,锋线的巴洛特利同样值得期待。世界杯最好战绩:冠军(1934、1938、1982、2006)";

	private static String Germany = "此前17次出战世界杯3次夺魁,但1990年后的5届比赛仅1次杀进决赛(日韩世界杯夺得亚军),本"
			+ "届世预赛他们与法罗群岛、奥地利、爱尔兰、瑞典以及哈萨克斯坦同组,取得9胜1平的骄人战绩,以小组头名晋级。该队主帅"
			+ "勒夫2006年开始执教,连续3届大赛率队杀进前四,拥有拉姆、施魏因斯泰格、罗伊斯、厄齐尔等大牌球星的德意志战车目标"
			+ "只有冠军。";
	private static String China = "2002年韩日世界杯首次参加世界杯,曾打出'进一球,平一场,得一分'的口号。结果一球未进,一分未得,"
			+ "世界杯最好战绩:第31名";

	public static void main(String[] args) {
		File file = new File("E:\\Football");
		try {
			Directory dir = new SimpleFSDirectory(file);
			Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_30);
			IndexWriter indexWriter = new IndexWriter(dir, analyzer, true,
					IndexWriter.MaxFieldLength.LIMITED);

			Document doc1 = new Document();
			Document doc2 = new Document();
			Document doc3 = new Document();
			Document doc4 = new Document();
			Document doc5 = new Document();
			Document doc6 = new Document();
			Document doc7 = new Document();
			Document doc8 = new Document();
			
			Field field10 = new Field("TeamNum", "00001", Field.Store.YES,
					Field.Index.ANALYZED);
			Field field11 = new Field("TeamName", "巴西", Field.Store.YES,
					Field.Index.ANALYZED);
			Field field12 = new Field("TeamBoss", "内马尔", Field.Store.YES,
					Field.Index.ANALYZED);
			Field field13 = new Field("TeamCoach", "斯科拉里", Field.Store.YES,
					Field.Index.ANALYZED);
			Field field14 = new Field("TeamInfo", Brazil, Field.Store.NO,
					Field.Index.ANALYZED);
			doc1.add(field10);
			doc1.add(field11);
			doc1.add(field12);
			doc1.add(field13);
			doc1.add(field14);

			Field field20 = new Field("TeamNum", "00002", Field.Store.YES,
					Field.Index.ANALYZED);
			Field field21 = new Field("TeamName", "阿根廷", Field.Store.YES,
					Field.Index.ANALYZED);
			Field field22 = new Field("TeamBoss", "梅西", Field.Store.YES,
					Field.Index.ANALYZED);
			Field field23 = new Field("TeamCoach", "萨维利亚", Field.Store.YES,
					Field.Index.ANALYZED);
			Field field24 = new Field("TeamInfo", Argentina, Field.Store.NO,
					Field.Index.ANALYZED);
			doc2.add(field20);
			doc2.add(field21);
			doc2.add(field22);
			doc2.add(field23);
			doc2.add(field24);

			Field field30 = new Field("TeamNum", "00003", Field.Store.YES,
					Field.Index.ANALYZED);
			Field field31 = new Field("TeamName", "荷兰", Field.Store.YES,
					Field.Index.ANALYZED);
			Field field32 = new Field("TeamBoss", "范佩西", Field.Store.YES,
					Field.Index.ANALYZED);
			Field field33 = new Field("TeamCoach", "范加尔", Field.Store.YES,
					Field.Index.ANALYZED);
			Field field34 = new Field("TeamInfo", Holland, Field.Store.NO,
					Field.Index.ANALYZED);
			doc3.add(field30);
			doc3.add(field31);
			doc3.add(field32);
			doc3.add(field33);
			doc3.add(field34);

			Field field40 = new Field("TeamNum", "00004", Field.Store.YES,
					Field.Index.ANALYZED);
			Field field41 = new Field("TeamName", "西班牙", Field.Store.YES,
					Field.Index.ANALYZED);
			Field field42 = new Field("TeamBoss", "伊涅斯塔", Field.Store.YES,
					Field.Index.ANALYZED);
			Field field43 = new Field("TeamCoach", "博斯克", Field.Store.YES,
					Field.Index.ANALYZED);
			Field field44 = new Field("TeamInfo", Spain, Field.Store.NO,
					Field.Index.ANALYZED);
			doc4.add(field40);
			doc4.add(field41);
			doc4.add(field42);
			doc4.add(field43);
			doc4.add(field44);

			Field field50 = new Field("TeamNum", "00005", Field.Store.YES,
					Field.Index.ANALYZED);
			Field field51 = new Field("TeamName", "法国", Field.Store.YES,
					Field.Index.ANALYZED);
			Field field52 = new Field("TeamBoss", "本泽马", Field.Store.YES,
					Field.Index.ANALYZED);
			Field field53 = new Field("TeamCoach", "德尚", Field.Store.YES,
					Field.Index.ANALYZED);
			Field field54 = new Field("TeamInfo", France, Field.Store.NO,
					Field.Index.ANALYZED);
			doc5.add(field50);
			doc5.add(field51);
			doc5.add(field52);
			doc5.add(field53);
			doc5.add(field54);

			Field field60 = new Field("TeamNum", "00006", Field.Store.YES,
					Field.Index.ANALYZED);
			Field field61 = new Field("TeamName", "意大利", Field.Store.YES,
					Field.Index.ANALYZED);
			Field field62 = new Field("TeamBoss", "巴洛特利", Field.Store.YES,
					Field.Index.ANALYZED);
			Field field63 = new Field("TeamCoach", "普兰德利", Field.Store.YES,
					Field.Index.ANALYZED);
			Field field64 = new Field("TeamInfo", Italy, Field.Store.NO,
					Field.Index.ANALYZED);
			doc6.add(field60);
			doc6.add(field61);
			doc6.add(field62);
			doc6.add(field63);
			doc6.add(field64);

			Field field70 = new Field("TeamNum", "00002000", Field.Store.YES,
					Field.Index.ANALYZED);
			Field field71 = new Field("TeamName", "德国", Field.Store.YES,
					Field.Index.ANALYZED);
			Field field72 = new Field("TeamBoss", "托马斯.穆勒", Field.Store.YES,
					Field.Index.ANALYZED);
			Field field73 = new Field("TeamCoach", "勒夫", Field.Store.YES,
					Field.Index.ANALYZED);
			Field field74 = new Field("TeamInfo", Germany, Field.Store.NO,
					Field.Index.ANALYZED);
			doc7.add(field70);
			doc7.add(field71);
			doc7.add(field72);
			doc7.add(field73);
			doc7.add(field74);

			Field field80 = new Field("TeamNum", "00001000", Field.Store.YES,
					Field.Index.ANALYZED);
			Field field81 = new Field("TeamName", "中国", Field.Store.YES,
					Field.Index.ANALYZED);
			Field field82 = new Field("TeamBoss", "郜林", Field.Store.YES,
					Field.Index.ANALYZED);
			Field field83 = new Field("TeamCoach", "阿兰.佩林", Field.Store.YES,
					Field.Index.ANALYZED);
			Field field84 = new Field("TeamInfo", China, Field.Store.NO,
					Field.Index.ANALYZED);
			doc8.add(field80);
			doc8.add(field81);
			doc8.add(field82);
			doc8.add(field83);
			doc8.add(field84);

			indexWriter.addDocument(doc1);
			indexWriter.addDocument(doc2);
			indexWriter.addDocument(doc3);
			indexWriter.addDocument(doc4);
			indexWriter.addDocument(doc5);
			indexWriter.addDocument(doc6);
			indexWriter.addDocument(doc7);
			indexWriter.addDocument(doc8);

			indexWriter.optimize();
			indexWriter.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}


一、TermQuery 词条搜索

Lucene内最为简单也最为原始的一种搜索方式

<span style="font-family:SimSun;font-size:12px;">import java.io.File;
import org.apache.lucene.index.Term;
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.SimpleFSDirectory;
public class LuceneTermQuery {

	public static void main(String[] args) {
		try {
			// 检索
			Directory dir = SimpleFSDirectory.open(new File("E:\\Football"));
			IndexSearcher indexSearcher = new IndexSearcher(dir);

			Term term = new Term("TeamInfo", "冠");// 在字段TermName包含“冠”字的文档

			Query query = new TermQuery(term);

			TopDocs topDocs = indexSearcher.search(query, 10);// 10
																// 代表取搜索结果评分最高的前10个,至于怎么评分的,我没搞清楚
			ScoreDoc[] scoreDocs = topDocs.scoreDocs;
			int totalLength = topDocs.totalHits;
			int scoreLength = scoreDocs.length;
			System.out.println("Total Hit:" + totalLength);// 满足条件个数
			System.out.println("Score Hit:" + scoreLength);// 实际返回个数
			System.out.println("******************************");
			for (int i = 0; i < scoreLength; i++) {
				System.out.println(indexSearcher.doc(scoreDocs[i].doc));
				System.out.println(scoreDocs[i].score);
				System.out.println("==============================");
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}</span>

执行结果控制台截图:




二、BooleanQuery 布尔搜索

Lucene的各种复杂查询最终都可以转换成boolean型的查询

1)、MUST表示搜索结果必须满足该Term

2) 、SHOULD表示搜索结果可以满足该Term

3) 、MUST_NOT表示搜索结果不许满足该Term(单独使用无意义)

关于这三个相互间连用会产生结果不写了,因为我感觉描述起来很容易让人产生误解,最好还是自己运行例子一次一次的试验结果,然后将自己理解的记住就行了

<span style="font-family:SimSun;font-size:12px;"><span style="font-family:SimSun;font-size:12px;">import java.io.File;
import java.io.IOException;

import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
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.SimpleFSDirectory;


public class LuceneBooleanQuery1 {
	public static void main(String[] args) {
		try {
			Directory dir= SimpleFSDirectory.open(new File("E:\\FootBall"));
			IndexSearcher indexSearcher =new IndexSearcher(dir);
			Term t1=new Term("TeamInfo","冠");
			Term t2=new Term("TeamName","国");
			Term t3=new Term("TeamBoss","西");
			Term t4=new Term("TeamBoss","林");
			
			Query tq1=new TermQuery(t1);
			Query tq2=new TermQuery(t2);
			Query tq3=new TermQuery(t3);
			Query tq4=new TermQuery(t4);

			
			BooleanQuery booleanQuery =new BooleanQuery();

			//条件满足 : TeamBoss中含有'西'的满足    TeamName中不含有'国'的满足     取二者交集
/*			booleanQuery.add(tq3,BooleanClause.Occur.SHOULD);
			booleanQuery.add(tq2,BooleanClause.Occur.MUST_NOT);*/
			
			//条件满足 :TeamBoss中含有'西'的满足  TeamName中含有'林'的满足            取二者并集
			booleanQuery.add(tq3,BooleanClause.Occur.SHOULD);
			booleanQuery.add(tq4,BooleanClause.Occur.SHOULD);
			
			TopDocs topDocs = indexSearcher.search(booleanQuery, 10);
			ScoreDoc[] scoreDocs = topDocs.scoreDocs;
			int totalLength = topDocs.totalHits;
			int scoreLength = scoreDocs.length;
			System.out.println("Total Hit:" + totalLength);
			System.out.println("Score Hit:" + scoreLength);
			System.out.println("******************************");
			for (int i = 0; i < scoreLength; i++) {
				System.out.println(indexSearcher.doc(scoreDocs[i].doc));
				System.out.println(scoreDocs[i].score);
				System.out.println("==============================");
			}
		} catch (CorruptIndexException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
		}
	}
}
</span></span>
执行结果控制台截图:



三、范围搜索 TermRangeQuery  

注意:TermRangeQuery针对的是字符串的范围搜索,而不是数字型的,举个例子 “000020000”  是在“00001”和“00004”之间的

import java.io.File;
import java.io.IOException;
import org.apache.lucene.search.*;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.SimpleFSDirectory;
public class LuceneRangeQuery {
	public static void main(String[] args) {
		try {
			Directory dir= SimpleFSDirectory.open(new File("E:\\FootBall"));
			IndexSearcher indexSearcher=new IndexSearcher(dir);
			
			//参数:1、要索引的字段  2、起始值 3、终点值 4、是否包含起始值(是否闭区间) 5、是否包含重点值
			TermRangeQuery q=new TermRangeQuery("TeamNum", "00001", "00005", true, true);
			TopDocs topDocs = indexSearcher.search(q, 10);// 10
			// 代表取搜索结果评分最高的前10个,至于怎么评分的,我没搞清楚
			ScoreDoc[] scoreDocs = topDocs.scoreDocs;
			int totalLength = topDocs.totalHits;
			int scoreLength = scoreDocs.length;
			System.out.println("Total Hit:" + totalLength);// 满足条件个数
			System.out.println("Score Hit:" + scoreLength);// 实际返回个数
			System.out.println("******************************");
			for (int i = 0; i < scoreLength; i++) {
			System.out.println(indexSearcher.doc(scoreDocs[i].doc));
			System.out.println(scoreDocs[i].score);
			System.out.println("==============================");
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
执行结果控制台截图:



可以看到,中国(00001000)和德国(00002000)也被检索出来了

在较早版本的Lucene,对一定范围内的查询所对应的查询对象是RangeQuery,然而其仅支持字符串形式的范围查询,因为Lucene 3.0提供了数字形式的范围查询NumericRangeQuery,所以原来的RangeQuery变为TermRangeQuery。

关于NumericRangeQuery不说了,用的时候再去查,直到有这个就行了。

四、前缀搜索  PrefixQuery

例如查找一本书,只记得书前面的几个字,就可以用前缀搜索的形式去查找

<span style="font-family:SimSun;font-size:12px;">import java.io.File;
import java.io.IOException;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.PrefixQuery;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.SimpleFSDirectory;
public class LucenePrefixQuery {
	public static void main(String[] args) {
		try {
			Directory dir = SimpleFSDirectory.open(new File("E:\\FootBall"));
			IndexSearcher indexSearcher=new IndexSearcher(dir);
			
			Term t=new Term("TeamName","阿");
			PrefixQuery q=new PrefixQuery(t);
			
			TopDocs topDocs = indexSearcher.search(q, 10);// 10
			// 代表取搜索结果评分最高的前10个,至于怎么评分的,我没搞清楚
			ScoreDoc[] scoreDocs = topDocs.scoreDocs;
			int totalLength = topDocs.totalHits;
			int scoreLength = scoreDocs.length;
			System.out.println("Total Hit:" + totalLength);// 满足条件个数
			System.out.println("Score Hit:" + scoreLength);// 实际返回个数
			System.out.println("******************************");
			for (int i = 0; i < scoreLength; i++) {
			System.out.println(indexSearcher.doc(scoreDocs[i].doc));
			System.out.println(scoreDocs[i].score);
			System.out.println("==============================");
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
  	
}
</span>
执行效果控制台截图:


其余的什么模糊搜索、短语搜索等等就不介绍了,用法大同小异。


PS:今天所有代码例子在用中文词条进行检索的时候都是用的单个字,那是因为选择的分词器就是一个标准的分词器,不支持中文分词。

有专门的中文分词器,也许下一次就会写到。







版权声明:本文为hai_cheng001原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。