Lucene 原理、API使用

日萌社

人工智能AI:Keras PyTorch MXNet TensorFlow PaddlePaddle 深度学习实战(不定时更新)


搜索引擎:Elasticsearch、Solr、Lucene


MySQL数据库的使用索引来搜索

1.MySQL数据库中的索引:
	UNIQUE(唯一索引)
	PRIMARY KEY(主键索引),本质是名叫PRIMARY KEY 的UNIQUE索引
	INDEX(普通索引)
	INDEX(组合索引)
	FULLTEXT(全文索引),搜索引擎常用

2.MySQL数据库的模糊查询速度慢,效率不高。
  模糊查询的用法分析:
	1.模糊查询的用法:字段名 like “%搜索内容%”
   	2.对“已添加索引的字段”进行模糊查询的话,分以下情况:
		1.使用 字段名 like “%搜索内容%”或 字段名 like “%搜索内容”的话,即模糊查询LIKE中以“%”或“_”开头的话,
		  是无法使用到索引进行快速查询的。
		2.使用 字段名 like “搜索内容%”,是可以使用到索引进行快速查询的。
 
3.即使字段添加了索引,但是在SQL语句中使用了以下规则进行WHERE条件过滤的话,便不会使用上“字段中添加的索引”进行快速查询:
	1.NULL值判断:因为使用NULL值判断的话,则不会使用字段中的索引。
		      所以字段的默认值应改为使用 0 或 -1 或 空字符串,字段的默认值不应为NULL值。
 	2.使用 != 或 <> 操作符:!= 和 <> 都表示不等于操作,则不会使用字段中的索引。应改为尽量使用 >、<、= 才会使用上字段中的索引。
	3.模糊查询LIKE中以“%”或“_”开头:是不会使用字段中的索引的。但如果以“%”或“_”结尾的话,还是可以使用到索引的。
	  				    可改为使用FULLTEXT(全文索引),搜索引擎常用。
	4.在where子句中使用 or 来连接条件:应使用UNION 或 UNION ALL 
	5.in 和 not in:尽量控制in的范围(1000以内)
	6.在where子句中对字段进行表达式操作:在列上进行运算的话,则不会使用字段中的索引

搜索引擎原理


倒排索引/反向索引 技术

1.单词:使用索引
2.倒排列表汇中:记录着的是文档编号
3.通过索引快速定义到“单词所对应的是”哪些文档编号,
  然后根据文档编号在对应的文档内容中进行搜索关键字内容。
1.倒排索引又叫反向索引(右下图)以字或词为关键字进行索引,表中关键字所对应的记录表项,
  记录了出现这个字或词的所有文档,每一个表项记录该文档的ID和关键字在该文档中出现的位置情况。
2.在实际的运用中,我们可以对数据库中原始的数据结构(临时表或者商品表),在业务空闲时事先根据左图内容,创建新的文档列表(左图)及倒排索引区域(右图)。
  用户有查询需求时,先访问倒排索引数据区域(右图),得出文档编号后,通过文档编号即可快速,准确的通过左图找到具体的文档内容。
  这一过程,可以通过我们自己写程序来实现,也可以借用已经抽象出来的通用开源技术来实现。
1.倒排建立索引的过程:
	1.将数据库表中的数据 原样保存到 索引库中(结构化数据)
	2.建立索引:
		1.中文分词:把一串中文内容分为多个词
		2.将每个词 和 对应的文档编号 进行关联起来,建立起索引

2.根据索引进行关键字查询:
	1.先根据索引进行关键字查询,如果有,获取文档编号。
	2.然后根据文档编号,去到对应的文档数据内容中 进行关键字查询。

3.正排索引:根据文档编号找到文档数据内容
4.倒排索引:根据关键字找到文档编号,然后再根据文档编号找到文档数据内容


全文检索

1.计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,
  当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式
2.全文检索:全部都搜索
3.通过分词和倒排索引实现全部都搜索
4.总结:对文档(数据)中每一个词都做索引。

Lucene

1.目前最新的版本是6.x系列,但是大多数企业中依旧使用4.x版本,比较稳定。本次课程我们使用4.10.2版本
2.Lucene与Solr的关系:
	Lucene:底层的API,工具包,提供了很多API实现建立索引和查询索引
	Solr:基于Lucene开发的企业级的搜索引擎产品

引入Lucene依赖

<dependencies>
		<!-- Junit单元测试 -->
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
		</dependency>
		<!-- lucene核心库 -->
		<dependency>
			<groupId>org.apache.lucene</groupId>
			<artifactId>lucene-core</artifactId>
			<version>4.10.2</version>
		</dependency>
		<!-- Lucene的查询解析器 -->
		<dependency>
			<groupId>org.apache.lucene</groupId>
			<artifactId>lucene-queryparser</artifactId>
			<version>4.10.2</version>
		</dependency>
		<!-- lucene的默认分词器库 -->
		<dependency>
			<groupId>org.apache.lucene</groupId>
			<artifactId>lucene-analyzers-common</artifactId>
			<version>4.10.2</version>
		</dependency>
		<!-- lucene的高亮显示 -->
		<dependency>
			<groupId>org.apache.lucene</groupId>
			<artifactId>lucene-highlighter</artifactId>
			<version>4.10.2</version>
		</dependency>
		<!-- 引入IK分词器 -->
		<dependency>
			<groupId>com.janeluo</groupId>
			<artifactId>ikanalyzer</artifactId>
			<version>2012_u6</version>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<!-- java编译插件 -->
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.2</version>
				<configuration>
					<source>1.7</source>
					<target>1.7</target>
					<encoding>UTF-8</encoding>
				</configuration>
			</plugin>
		</plugins>
	</build>


创建索引

1.索引库:实际就是本地的一个文件夹目录
2.创建索引写入器配置对象:IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST, new StandardAnalyzer());
	第一个参数:配置Lucene的版本,可使用“VerSion.LATEST”表示最新版本
	第二个参数:指定使用哪种分词器。此处创建使用默认标准的分词器对象:Analyzer analyzer = new StandardAnalyzer()
	注意:默认标准的分词器是不支持中文分词的,所以使用默认标准的分词器对象的话,
	      那么一串的中文内容的话,会以一个一个中文字符进行分词,而不是以词的形式进行分词。
3.索引写入器对象:IndexWriter indexWriter = new IndexWriter(“索引库的存放位置”Directory目录对象, 索引写入器配置对象);
  索引写入器对象写入完数据之后,必须close()关闭:indexWriter.close() 
示例代码:
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.document.Document;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
import org.junit.Test;


		// 创建文档对象
		Document document = new Document();
		// 使用StringField/TextField封装字段数据:Field是一个父类,StringField或TextField都继承自Field
		// StringField构造器中的3个参数:1.字段名,2.字段值,3.是否作为文档数据保存(Store.YES存储;NO不存储。)
		// StringField创建索引但不分词
		document.add(new StringField("id", "1", Store.YES));
		// TextField构造器中的3个参数:1.字段名,2.字段值,3.是否作为文档数据保存(Store.YES存储;NO不存储。)
		// TextField:创建索引并进行分词 
		document.add(new TextField("title", "谷歌地图之父跳槽FaceBook", Store.YES));

		// 创建Directory目录对象,指定索引库的存放位置,有以下两种存放方式:
		// 		1.FSDirectory:文件系统目录,会把索引库保存到本地磁盘。特点:速度略慢,但是比较安全
		// 		2.RAMDirectory:内存目录,会把索引库保存在内存。 特点:速度快,但是不安全
		Directory directory = FSDirectory.open(new File("F:\\LucenenData\\indexDir"));
		// 创建分词器对象 
		// 默认分词器new StandardAnalyzer():默认对英文进行分词,不会对中文进行中文分词,中文只能以逐个中文字符作为分词
		Analyzer analyzer = new StandardAnalyzer();
		// 创建索引写入器配置对象:
		//		第一个参数:Lucene版本号 VerSion.LATEST
		//		第一个参数:分词器对象
		IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST, analyzer);
		// 创建索引写入器:传入Directory目录对象 和 索引写入器配置对象
		IndexWriter indexWriter = new IndexWriter(directory, conf);
		// 向索引库写入文档对象
		indexWriter.addDocument(document);
		// 提交
		indexWriter.commit();
		// 关闭
		indexWriter.close();





批量添加索引
		List<Document> docs = new ArrayList<Document>();
		for(Long i = 2L ;i < 30 ; i ++)
		{
			Document doc = new Document();
			LongField id = new LongField("id", i, Store.YES);
			doc.add(id);
			StringField title = new StringField("title", "Itcast"+i, Store.YES);
			doc.add(title);
			TextField content = new TextField("content", "鸟哥的linux技术私房菜"+i, Store.YES);
			if(i==17L){
				//激励因子  改变激励因子可以改变得分
				content.setBoost(10);
			}
			doc.add(content);
			docs.add(doc);//把每个Document文档对象添加到List<Document>集合中
		}
		
		//批量添加索引:往索引写入器中传入List<Document>集合
		indexWriter.addDocuments(docs);
		// 提交
		indexWriter.commit();
		// 关闭
		indexWriter.close();


Lucene索引查看工具lukeall


API详解

 

Document(文档类)

 

Field(字段类)

1.一个Document文档对象中中可以存储多个不同的字段,每一个字段都是一个Field类的对象。
2.一个Document文档对象中的字段其类型是不确定的,因此Field类就提供了各种不同的子类,来对应这些不同类型的字段。
3.此Field类的子类如下:

4.Field类的每个子类有不同的特性:
	1.创建索引:
		DoubleField、FloatField、IntField、LongField、StringField、TextField这些子类都一定会被创建索引。

	2.创建索引,并且会自动分词:
		TextField:既会创建索引,又会自动分词。

	3.创建索引,但并不会分词:
		StringField:会创建索引,但是不会分词。
		如果不分词,会造成整个字段值作为整体一个词条,除非用户在搜索时完全匹配整个字段值,否则搜索不到。

	4.自动会存储,但是一定不会创建索引,也不会分词:
		StoredField:可以创建各种数据类型的字段 

	5.封装在Document文档对象中的字段数据作为文档内容数据存储到本地文件中:
		要通过构造函数中的参数Store来指定:Store.YES/Store.NO 
			Store.YES:代表存储
  			Store.NO:代表不存储
	 



1.如何确定一个字段是否需要存储?
  如果一个字段要显示到最终的结果中展示给用户看,那么一定要存储,否则就不存储。
  比如一个商品描述很长,在查询的时候又没必要显示到结果中展示给用户看的话,可以不存储。

2.Store.YES/Store.NO:
	Store.YES:代表存储
  	Store.NO:代表不存储

3.Store.NO的使用场景:
	1.我的一个Document文档对象存储有三个字段名:商品id、商品title、商品描述desc
	2.id 和 title都使用Store.YES都作为文档内容进行存储,而商品描述desc使用Store.NO不作为文档内容进行存储,
	  那么通过索引查询出来的数据只有id 和 title值,没有商品描述desc数据。因为商品描述desc数据可能很大,
	  没必要作为文档内容进行存储,而且我们搜索出来的结果根据需求也没有必要显示出来。所以商品描述desc数据并没有作为文档内容进行存储。
	3.既然商品描述desc数据并不作为文档内容进行存储,那为什么还要在Document文档对象中存储商品描述desc数据信息呢?
	  虽然商品描述desc数据并不作为文档内容进行存储,但是只要是把商品描述desc数据往Document文档对象中进行存储时,
	  都会自动给 商品描述desc数据进行分词并建立索引。
	  好处结果便是:在搜索时,通过搜索商品信息的关键字便可根据分词索引查找到对应的Document文档对象中存储的其他数据,
	  包括查询出商品id、商品title信息数据,那么接着便可以根据商品id到MySQL数据库中查找出其他相应的商品信息。

4.如何确定一个字段是否需要创建索引?
	如果要根据这个字段进行搜索,那么这个字段就必须创建索引。

5.如何确定一个字段是否需要分词?
	前提是这个字段首先要创建索引。然后如果这个字段的值是不可分割的,那么就不需要分词。例如:ID

Directory(目录类)

1.FSDirectory:文件系统目录,会把索引库保存到本地磁盘。
	 特点:速度略慢,但是比较安全
2.RAMDirectory:内存目录,会把索引库保存在内存。
	  特点:速度快,但是不安全


IndexWriterConfig(索引写入器配置类)

1.设置配置信息:Lucene的版本和分词器类型

2.设置OpenMode参数

1.OpenMode.APPEND:默认参数,一般场景都是索引追加。
	索引追加:
		1.当新的分词要存进索引库中时,如果索引库中已存在同名的分词时,则索引追加,在该已存在的分词对应的倒排列表中追加新的文档编号;
		  使用Lucene索引查看工具lukeall可以查看到	该分词所在的Freq列上的值会是追加自增
		2.如果索引库中不存在同名的分词时,则索引创建,在索引库中直接添加该分词,并在新存入的分词对应的倒排列表中添加文档编号。
2.OpenMode.CREATE:
	索引覆盖/索引创建:
		1.当新的分词要存进索引库中时,如果索引库中已存在同名的分词时,则索引覆盖,把该已存在的分词对应的倒排列表中所有的文档编号给删除掉后,保存新的文档编号。
		  使用Lucene索引查看工具lukeall可以查看到	该分词所在的Freq列上的旧值会被覆盖为新值。
		2.如果索引库中不存在同名的分词时,则索引创建,在索引库中直接添加该分词,并在新存入的分词对应的倒排列表中添加文档编号。
		  OpenMode打开模式:OpenMode枚举类。1.OpenMode.CREATE:索引覆盖。  2.OpenMode.APPEND:索引追加	

IndexWriter(索引写入器类)

1.IndexWriter(索引写入器类):
	索引写出工具,作用就是 实现对索引的增(创建索引)、删(删除索引)、改(修改索引)
 
 
2.索引写入工具IndexWriter的使用注意:
	1.IndexWriter也是全局唯一(单例模式),则在使用IndexWriter时无需关闭
     	2.问题:若IndexWriter使用完毕之后不关闭,那么会导致新建的索引没有生效?
     	3.解决办法:使用indexWriter.commit()方法提交对索引的更改!

3.示例代码:
		// 创建Directory目录对象,指定索引库的存放位置,有以下两种存放方式:
		// 		1.FSDirectory:文件系统目录,会把索引库保存到本地磁盘。特点:速度略慢,但是比较安全
		// 		2.RAMDirectory:内存目录,会把索引库保存在内存。 特点:速度快,但是不安全
		Directory directory = FSDirectory.open(new File("F:\\LucenenData\\indexDir"));
		// 创建分词器对象 
		// 默认分词器new StandardAnalyzer():默认对英文进行分词,不会对中文进行中文分词,中文只能以逐个中文字符作为分词
		Analyzer analyzer = new StandardAnalyzer();
		// 创建索引写入器配置对象:
		//		第一个参数:Lucene版本号 VerSion.LATEST
		//		第一个参数:分词器对象
		IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST, analyzer);
		// 创建索引写入器:传入Directory目录对象 和 索引写入器配置对象
		IndexWriter indexWriter = new IndexWriter(directory, conf);
		// 向索引库写入文档对象
		indexWriter.addDocument(Document文档对象);
		// 提交
		indexWriter.commit();
		// 关闭
		indexWriter.close();

DirectoryReader/IndexReader(索引读取工具类)

1.DirectoryReader/IndexReader(索引读取工具类)的开启和关闭:
	1.开启读取(打开硬盘索引):
		//第一种用法:索引读取工具DirectoryReader(DirectoryReader的父类是IndexReader) 
		DirectoryReader reader = DirectoryReader.open(directory);
		//第二种用法:索引读取工具IndexReader(IndexReader的其中一个子类是DirectoryReader) 
		IndexReader reader = DirectoryReader.open(directory);

		//第一种关闭用法:使用索引搜索工具IndexSearcher获取索引读取工具IndexReader对象,并调用close()方法
		IndexReader reader = IndexSearcher实例对象.getIndexReader() 
		reader.close() 

 	2.关闭读取(关闭硬盘索引):
		//class IndexReader implements Closeable:IndexReader类中带有close()方法,那么子类DirectoryReader都继承有close()方法
		//第二种关闭用法:IndexReader类或DirectoryReader类的实例对象都可以调用close()
		reader.close();

2.IndexReader的使用注意:
	1.IndexReader的实例化过程是一个非常耗时的过程。
	  在使用Lucene时,当索引的数据很大时,建议不要频繁去打开关闭硬盘索引(),这个加载过程就比较耗时了。
	2.在搜索索引时,创建IndexSearcher对象即可。一般全局唯一(单例模式),无需关闭。
	3.创建 索引读取工具IndexReader:
		1.问题:若IndexReader全局唯一,则当我们在搜索索引的过程中,索引发生改变(比如:用IndexWriter删除某个索引),
			但是搜索到的索引并没有改变?
		2.原因:因为IndexReader全局唯一,它始终读取的是最开始创建的索引。
		3.解决办法:
			1.使用 IndexReader ir = IndexReader.openIfChanged(IndexReader oldReader),用来读取最新的索引。
			2.IndexReader.openIfChanged()方法:若IndexReader打开后,如果索引发生了变化的话,
			  那么使用openIfChanged方法则会返回一个新的IndexReader,然后把旧的IndexReader对象关闭(旧的IndexReader对象名.close());
			  如果索引没有发生变化的话,则返回null。
			3.IndexReader 新的IndexReader对象 = IndexReader.openIfChanged(旧的IndexReader对象) 

3.示例代码1:
	//创建 索引读取工具IndexReader
	public IndexSearcher createIndexSearcher()
	{
        	try 
		{
            		if (indexReader == null)
			{
                		indexReader = IndexReader.open(directory);
            		} 
			else 
			{
				//1.使用 IndexReader ir = IndexReader.openIfChanged(IndexReader oldReader),用来读取最新的索引。
				//2.IndexReader.openIfChanged()方法:若IndexReader打开后,如果索引发生了变化的话,
			  	//  那么使用openIfChanged方法则会返回一个新的IndexReader,然后把旧的IndexReader对象close()关闭;
				//  如果索引没有发生变化的话,则返回null。
				//3.IndexReader 新的IndexReader对象 = IndexReader.openIfChanged(旧的IndexReader对象) 
                		IndexReader ir = IndexReader.openIfChanged(indexReader);
                		if (ir != null)
				{
                    			//将旧的IndexReader对象关闭
                    			indexReader.close();
                    			indexReader = ir;
               	 		}
           		 }
        	} 
		catch (IOException e) 
		{
            		e.printStackTrace();
       		}
    		return new IndexSearcher(indexReader);
	}

4.示例代码2:	
		//第一种查询解析器:查询单字段的查询解析器
	 	//查询解析器对象(构造参数:1.搜索的目标字段名  2.使用何种分词器对搜索的参数进行分析)
		//此处使用中文分词器IKAnalyzer 对"content"字段数据进行搜索
		QueryParser queryParser = new QueryParser("content", new IKAnalyzer());
		//第二种查询解析器:查询多字段的查询解析器。会把"要搜索的词句中的"每个分词赋值给多个"搜索的目标字段名"进行搜索
		//MultiFieldQueryParser queryParser = new MultiFieldQueryParser(new String[]{"id","content"}, new IKAnalyzer());

		//查询对象,包含要查询的关键词信息。首先会先对"要搜索的词句"进行分词,然后再把每个分词赋值给单个/多个"搜索的目标字段名"
		Query query = queryParser.parse("要搜索的词句");
		// 索引库目录文件夹:"c:\\index"
		Directory directory = FSDirectory.open(new File("c:\\index"));

		//第一种用法:索引读取工具DirectoryReader(DirectoryReader的父类是IndexReader) 
		DirectoryReader reader = DirectoryReader.open(directory);
		//第二种用法:索引读取工具IndexReader(IndexReader的其中一个子类是DirectoryReader) 
		//IndexReader reader = DirectoryReader.open(directory);

		// 索引搜索工具IndexSearcher 
		IndexSearcher indexSearcher = new IndexSearcher(reader);
		//使用索引搜索工具IndexSearcher 根据Query查询对象查询获取匹配度最高的前面n条文档内容信息
		//TopDocs topDocs = indexSearcher.search(Query查询对象, 获得匹配度最高的前面n条文档内容信息) 
		TopDocs topDocs = indexSearcher.search(query, 10) 
		//返回值TopDocs查询结果对象中封装了两部分数据:	
		//	1.TopDocs中的第一部分数据:
		//		int totalHits = topDocs.totalHits
		//		查询到的数据库中匹配到的的总条数
		//	2.TopDocs中的第二部分数据:
		//		ScoreDoc[] scoreDocs = topDocs.scoreDocs
		//		ScoreDoc数组中封装了匹配度最高的前面n条文档内容信息。
		//totalHits:查询获取出来的到的总条数
		int totalHits = topDocs.totalHits;
		//ScoreDoc[]数组中的每个ScoreDoc对象 封装了两部分数据:
		//	1.ScoreDoc对象中第一部分数据:float score = scoreDoc.score:文档得分
		//				      文档得分越高表示匹配度越高,文档得分越低表示匹配度越低。
		//	2.ScoreDoc对象中第二部分数据:int docId = scoreDoc.doc:文档编号
		ScoreDoc[] scoreDocs = topDocs.scoreDocs;
		for (ScoreDoc scoreDoc : scoreDocs) 
		{
			//ScoreDoc对象中第一部分数据:文档得分(ScoreDoc对象名.score)
			float score = scoreDoc.score;
			//ScoreDoc对象中第二部分数据:文档编号(ScoreDoc对象名.doc)
			int docId = scoreDoc.doc;
			//根据文档编号获取到Document文档对象:使用索引搜索工具IndexSearcher根据文档编号 找到对应的Document文档对象
			Document document = indexSearcher.doc(docId); 
			//Document文档对象名.get(“字段名”):获取字段值
			System.out.println(document.get("id"));
			System.out.println(document.get("desc"));
			System.out.println(document.get("title"));
		}

		//第一种关闭用法:使用索引搜索工具IndexSearcher获取索引读取工具IndexReader对象,并调用close()方法
		IndexReader reader = indexSearcher.getIndexReader() 
		reader.close() 
 
		//class IndexReader implements Closeable:IndexReader类中带有close()方法,那么子类DirectoryReader都继承有close()方法
		//第二种关闭用法:IndexReader类或DirectoryReader类的实例对象都可以调用close()
		reader.close();


Analyzer分词器:IK分词器(中文分词器)

1.第一步:pom.xml中引入IK分词器(中文分词器)		
		<!-- 引入IK分词器 -->
		<dependency>
			<groupId>com.janeluo</groupId>
			<artifactId>ikanalyzer</artifactId>
			<version>2012_u6</version>
		</dependency>

2.第二步:/resources/IKAnalyzer.cfg.xml文件 
	<?xml version="1.0" encoding="UTF-8"?>
	<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">  
	<properties>  
		<comment>IK Analyzer 扩展配置</comment>

		<!--用户可以在这里配置自己的扩展字典 -->
		<entry key="ext_dict">ext.dic;</entry> 
	
		<!--用户可以在这里配置自己的扩展停止词字典-->
		<entry key="ext_stopwords">stopword.dic;</entry> 
	</properties>
	
3.第三步:/resources/ext.dic文件
		一行写一个分词,如以下写法: 

IK分词器(中文分词器):
	1.引入IK分词器的依赖:
		<!-- 引入IK分词器 -->
		<dependency>
			<groupId>com.janeluo</groupId>
			<artifactId>ikanalyzer</artifactId>
			<version>2012_u6</version>
		</dependency>

	2.中文分词器(IK分词器)
		Analyzer analyzer = new IKAnalyzer();


	3.自定义词库
		1.扩展词典(新创建词功能): 有些词IK分词器不识别 例如:“传智播客”,“碉堡了”
		2.停用词典(停用某些词功能): 有些词不需要建立索引  例如:“哦”,“啊”,“的”
		3.IK分词器的词库有限,新增加的词条可以通过配置文件添加到IK的词库中,也可以把一些不用的词条去除:

		4.扩展词典:用来引入一些自定义的新词

		5.停止词典:用来停用一些不必要的词条

		6.结果:分词中,加入了我们新的词,被停用的词语没有被分词:

	@Test
	public void indexCreate1() throws IOException 
	{
		// 索引库位置
		Directory directory = FSDirectory.open(new File("F:\\LucenenData\\indexDir"));

		// 创建分词器对象
		// 默认分词器new StandardAnalyzer():默认对英文进行分词,不会对中文进行中文分词,中文只能以逐个中文字符作为分词
//		Analyzer analyzer = new StandardAnalyzer();
		//中文分词器(IK分词器)
		Analyzer analyzer = new IKAnalyzer();
		
		// 创建索引写入器配置对象,第一个参数Lucene版本,第二个参数分词器
		//Lucene最新版本:VerSion.LATEST。默认分词器:new StandardAnalyzer()
		IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST, analyzer);
		// 创建索引写入器对象:传入Directory目录对象 和 索引写入器配置对象
		IndexWriter indexWriter = new IndexWriter(directory, conf);

		// 向索引库写入Document文档对象
		// 准备数据 数据封装在Document对象中
		Document doc = new Document();
		LongField id = new LongField("id", 2L, Store.YES);
		doc.add(id);
		TextField title = new TextField("title", "谷歌地图之父跳槽facebook", Store.YES);
		doc.add(title);
		DoubleField price = new DoubleField("price", 78.26, Store.YES);
		doc.add(price);
		
		indexWriter.addDocument(doc);// 向索引库写入文档对象
		indexWriter.commit();// 提交
		indexWriter.close();// 关闭
	}

 
	@Test
	public void indexCreateAPI() throws IOException {
		// 准备数据 数据封装在Document对象中
		Document doc = new Document();

		LongField id = new LongField("id", 1L, Store.NO);
		doc.add(id);

		StringField content = new StringField("content", "程序员", Store.NO);
		doc.add(content);

		// 索引库位置
		Directory directory = FSDirectory.open(new File("F:\\LucenenData\\indexDir"));

		// 创建分词器对象
		// 默认分词器new StandardAnalyzer():默认对英文进行分词,不会对中文进行中文分词,中文只能以逐个中文字符作为分词
//		Analyzer analyzer = new StandardAnalyzer();
		//中文分词器(IK分词器)
		Analyzer analyzer = new IKAnalyzer();
		
		// 创建索引写入器配置对象,第一个参数版本VerSion.LATEST,第二个参数分词器
		//Lucene最新版本:VerSion.LATEST。默认分词器:new StandardAnalyzer()
		IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST,analyzer );
		// 创建索引写入器对象
		IndexWriter indexWriter = new IndexWriter(directory, conf);

		// 向索引库写入文档对象
		indexWriter.addDocument(doc);// 向索引库写入文档对象
		indexWriter.commit();// 提交
		indexWriter.close();// 关闭
	}

	@Test
	public void testCreater2() throws IOException {

		// 创建文档对象集合
		List<Document> docs = new ArrayList<>();

		// 创建文档对象
		Document document1 = new Document();
		document1.add(new StringField("id", "1", Store.YES));
		document1.add(new TextField("title", "谷歌地图之父跳槽FaceBook", Store.YES));
		docs.add(document1);
		// 创建Document文档对象
		Document document2 = new Document();
		document2.add(new StringField("id", "2", Store.YES));
		document2.add(new TextField("title", "谷歌地图之父加盟FaceBook", Store.YES));
		docs.add(document2);
		// 创建文档对象
		Document document3 = new Document();
		document3.add(new StringField("id", "3", Store.YES));
		document3.add(new TextField("title", "谷歌地图创始人拉斯离开谷歌加盟Facebook", Store.YES));
		docs.add(document3);
		// 创建文档对象
		Document document4 = new Document();
		document4.add(new StringField("id", "4", Store.YES));
		document4.add(new TextField("title", "谷歌地图之父跳槽Facebook与Wave项目取消有关", Store.YES));
		docs.add(document4);
		// 创建文档对象
		Document document5 = new Document();
		document5.add(new StringField("id", "5", Store.YES));
		document5.add(new TextField("title", "谷歌地图之父拉斯加盟社交网站Facebook", Store.YES));
		docs.add(document5);

		// 索引库对象
		Directory directory = FSDirectory.open(new File("F:\\LucenenData\\indexDir"));
		
		// 创建分词器对象
		// 默认分词器new StandardAnalyzer():默认对英文进行分词,不会对中文进行中文分词,中文只能以逐个中文字符作为分词
//		Analyzer analyzer = new StandardAnalyzer();
		//中文分词器(IK分词器)
		Analyzer analyzer = new IKAnalyzer();
		
		// 创建索引写入器配置对象,第一个参数版本VerSion.LATEST,第二个参数分词器
		//Lucene最新版本:VerSion.LATEST。默认分词器:new StandardAnalyzer()
		IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST, analyzer);
		//1.OpenMode.APPEND:默认参数,一般场景都是索引追加。
		//		索引追加:
		//			1.当新的分词要存进索引库中时,如果索引库中已存在同名的分词时,则索引追加,在该已存在的分词对应的倒排列表中追加新的文档编号;
		//			  使用Lucene索引查看工具lukeall可以查看到	该分词所在的Freq列上的值会是追加自增
		//			2.如果索引库中不存在同名的分词时,则索引创建,在索引库中直接添加该分词,并在新存入的分词对应的倒排列表中添加文档编号。
		//2.OpenMode.CREATE:
		//		索引覆盖/索引创建:
		//			1.当新的分词要存进索引库中时,如果索引库中已存在同名的分词时,则索引覆盖,把该已存在的分词对应的倒排列表中所有的文档编号给删除掉后,
		//			  保存新的文档编号。
		//			  使用Lucene索引查看工具lukeall可以查看到	该分词所在的Freq列上的旧值会被覆盖为新值。
		//			2.如果索引库中不存在同名的分词时,则索引创建,在索引库中直接添加该分词,并在新存入的分词对应的倒排列表中添加文档编号。
		// OpenMode打开模式:OpenMode枚举类。1.OpenMode.CREATE:索引覆盖。  2.OpenMode.APPEND:索引追加	
		conf.setOpenMode(OpenMode.CREATE);
		// 创建索引写入器对象:传入Directory目录对象 和 索引写入器配置对象
		IndexWriter indexWriter = new IndexWriter(directory, conf);

		// 执行写入操作
		indexWriter.addDocuments(docs);
		// 提交
		indexWriter.commit();
		// 关闭
		indexWriter.close();
	}

	@Test
	public void indexCreateIK() throws IOException {
		// 准备数据 数据封装在Document对象中
		Document doc = new Document();

		LongField id = new LongField("id", 5L, Store.NO);
		doc.add(id);
		TextField title = new TextField("title", "华为手机", Store.YES);
		doc.add(title);
		TextField desc = new TextField("desc", "充电一小时,通话一分钟", Store.YES);
		doc.add(desc);
		DoubleField price = new DoubleField("price", 9999, Store.YES);
		doc.add(price);
 
		// 索引库位置
		Directory directory = FSDirectory.open(new File("F:\\LucenenData\\indexDir"));

		// 创建索引写入器配置对象,第一个参数版本VerSion.LATEST,第二个参数分词器
		IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST, new IKAnalyzer());
		// 创建索引写入器对象
		IndexWriter indexWriter = new IndexWriter(directory, conf);

		// 向索引库写入文档对象
		indexWriter.addDocument(doc);
		indexWriter.commit();
		indexWriter.close();
	}


	@Test
	public void indexCreate() throws IOException {
		// 准备数据  数据封装在Document对象中
		Document doc = new Document();
		//数据有不同字段,字段类型也不同
		//LongField IntField DoubleField....
		// StringField(索引但是默认不分词,数据被当成一个词条), Term(词条)
		// TextField(索引并且根据索引写入器中定义的分词器来进行分词,数据被当做多个词条)
		LongField id = new LongField("id", 1L, Store.YES);
		doc.add(id);
//		new TextField("title", "谷歌地图之父跳槽FaceBook", Store.YES);
		// TextField:创建索引并提供分词,StringField创建索引但不分词
		TextField content = new TextField("content", "传智播客是个好地方,上海校区更加好", Store.YES);
		doc.add(content);
		
		// 创建目录对象,指定索引库的存放位置;FSDirectory文件系统;RAMDirectory内存
		Directory directory = FSDirectory.open(new File("F:\\LucenenData\\indexDir"));
		// 创建分词器对象
//		StandardAnalyzer analyzer = new StandardAnalyzer();
		Analyzer analyzer = new IKAnalyzer();
		// 创建索引写入器配置对象,第一个参数版本VerSion.LATEST,第一个参数分词器
		IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST, analyzer);
		// 创建索引写入器
		IndexWriter indexWriter = new IndexWriter(directory , conf);
		// 向索引库写入文档对象
//		indexWriter.addDocument(doc);
		List<Document> docs = new ArrayList<Document>();
		docs.add(doc);
		//批量添加索引
		indexWriter.addDocuments(docs);
		// 提交
		indexWriter.commit();
		// 关闭
		indexWriter.close();
	}


根据索引查询数据

 

DirectoryReader/IndexReader(索引读取工具类)

1.DirectoryReader/IndexReader(索引读取工具类)的开启和关闭:
	1.开启读取(打开硬盘索引):
		//第一种用法:索引读取工具DirectoryReader(DirectoryReader的父类是IndexReader) 
		DirectoryReader reader = DirectoryReader.open(directory);
		//第二种用法:索引读取工具IndexReader(IndexReader的其中一个子类是DirectoryReader) 
		IndexReader reader = DirectoryReader.open(directory);

		//第一种关闭用法:使用索引搜索工具IndexSearcher获取索引读取工具IndexReader对象,并调用close()方法
		IndexReader reader = IndexSearcher实例对象.getIndexReader() 
		reader.close() 

 	2.关闭读取(关闭硬盘索引):
		//class IndexReader implements Closeable:IndexReader类中带有close()方法,那么子类DirectoryReader都继承有close()方法
		//第二种关闭用法:IndexReader类或DirectoryReader类的实例对象都可以调用close()
		reader.close();

2.IndexReader的使用注意:
	1.IndexReader的实例化过程是一个非常耗时的过程。
	  在使用Lucene时,当索引的数据很大时,建议不要频繁去打开关闭硬盘索引(),这个加载过程就比较耗时了。
	2.在搜索索引时,创建IndexSearcher对象即可。一般全局唯一,无需关闭。
	3.创建 索引读取工具IndexReader:
		1.问题:若IndexReader全局唯一,则当我们在搜索索引的过程中,索引发生改变(比如:用IndexWriter删除某个索引),
			但是搜索到的索引并没有改变?
		2.原因:因为IndexReader全局唯一,它始终读取的是最开始创建的索引。
		3.解决办法:
			1.使用 IndexReader ir = IndexReader.openIfChanged(IndexReader oldReader),用来读取最新的索引。
			2.IndexReader.openIfChanged()方法:若IndexReader打开后,如果索引发生了变化的话,
			  那么使用openIfChanged方法则会返回一个新的IndexReader,然后把旧的IndexReader对象关闭(旧的IndexReader对象名.close());
			  如果索引没有发生变化的话,则返回null。
			3.IndexReader 新的IndexReader对象 = IndexReader.openIfChanged(旧的IndexReader对象) 

3.示例代码1:
	//创建 索引读取工具IndexReader
	public IndexSearcher createIndexSearcher()
	{
        	try 
		{
            		if (indexReader == null)
			{
                		indexReader = IndexReader.open(directory);
            		} 
			else 
			{
				//1.使用 IndexReader ir = IndexReader.openIfChanged(IndexReader oldReader),用来读取最新的索引。
				//2.IndexReader.openIfChanged()方法:若IndexReader打开后,如果索引发生了变化的话,
			  	//  那么使用openIfChanged方法则会返回一个新的IndexReader,然后把旧的IndexReader对象close()关闭;
				//  如果索引没有发生变化的话,则返回null。
				//3.IndexReader 新的IndexReader对象 = IndexReader.openIfChanged(旧的IndexReader对象) 
                		IndexReader ir = IndexReader.openIfChanged(indexReader);
                		if (ir != null)
				{
                    			//将旧的IndexReader对象关闭
                    			indexReader.close();
                    			indexReader = ir;
               	 		}
           		 }
        	} 
		catch (IOException e) 
		{
            		e.printStackTrace();
       		}
    		return new IndexSearcher(indexReader);
	}

4.示例代码2:	
		//第一种查询解析器:查询单字段的查询解析器
	 	//查询解析器对象(构造参数:1.搜索的目标字段名  2.使用何种分词器对搜索的参数进行分析)
		//此处使用中文分词器IKAnalyzer 对"content"字段数据进行搜索
		QueryParser queryParser = new QueryParser("content", new IKAnalyzer());
		//第二种查询解析器:查询多字段的查询解析器。会把"要搜索的词句中的"每个分词赋值给多个"搜索的目标字段名"进行搜索
		//MultiFieldQueryParser queryParser = new MultiFieldQueryParser(new String[]{"id","content"}, new IKAnalyzer());

		//查询对象,包含要查询的关键词信息。首先会先对"要搜索的词句"进行分词,然后再把每个分词赋值给单个/多个"搜索的目标字段名"
		Query query = queryParser.parse("要搜索的词句");
		// 索引库目录文件夹:"c:\\index"
		Directory directory = FSDirectory.open(new File("c:\\index"));

		//第一种用法:索引读取工具DirectoryReader(DirectoryReader的父类是IndexReader) 
		DirectoryReader reader = DirectoryReader.open(directory);
		//第二种用法:索引读取工具IndexReader(IndexReader的其中一个子类是DirectoryReader) 
		//IndexReader reader = DirectoryReader.open(directory);

		// 索引搜索工具IndexSearcher 
		IndexSearcher indexSearcher = new IndexSearcher(reader);
		//使用索引搜索工具IndexSearcher 根据Query查询对象查询获取匹配度最高的前面n条文档内容信息
		//TopDocs topDocs = indexSearcher.search(Query查询对象, 获得匹配度最高的前面n条文档内容信息) 
		TopDocs topDocs = indexSearcher.search(query, 10) 
		//返回值TopDocs查询结果对象中封装了两部分数据:	
		//	1.TopDocs中的第一部分数据:
		//		int totalHits = topDocs.totalHits
		//		查询到的数据库中匹配到的的总条数
		//	2.TopDocs中的第二部分数据:
		//		ScoreDoc[] scoreDocs = topDocs.scoreDocs
		//		ScoreDoc数组中封装了匹配度最高的前面n条文档内容信息。
		//totalHits:查询获取出来的到的总条数
		int totalHits = topDocs.totalHits;
		//ScoreDoc[]数组中的每个ScoreDoc对象 封装了两部分数据:
		//	1.ScoreDoc对象中第一部分数据:float score = scoreDoc.score:文档得分
		//				      文档得分越高表示匹配度越高,文档得分越低表示匹配度越低。
		//	2.ScoreDoc对象中第二部分数据:int docId = scoreDoc.doc:文档编号
		ScoreDoc[] scoreDocs = topDocs.scoreDocs;
		for (ScoreDoc scoreDoc : scoreDocs) 
		{
			//ScoreDoc对象中第一部分数据:文档得分(ScoreDoc对象名.score)
			float score = scoreDoc.score;
			//ScoreDoc对象中第二部分数据:文档编号(ScoreDoc对象名.doc)
			int docId = scoreDoc.doc;
			//根据文档编号获取到Document文档对象:使用索引搜索工具IndexSearcher根据文档编号 找到对应的Document文档对象
			Document document = indexSearcher.doc(docId); 
			//Document文档对象名.get(“字段名”):获取字段值
			System.out.println(document.get("id"));
			System.out.println(document.get("desc"));
			System.out.println(document.get("title"));
		}

		//第一种关闭用法:使用索引搜索工具IndexSearcher获取索引读取工具IndexReader对象,并调用close()方法
		IndexReader reader = indexSearcher.getIndexReader() 
		reader.close() 
 
		//class IndexReader implements Closeable:IndexReader类中带有close()方法,那么子类DirectoryReader都继承有close()方法
		//第二种关闭用法:IndexReader类或DirectoryReader类的实例对象都可以调用close()
		reader.close();
1.查询解析器 
	1.QueryParser(单一字段的查询解析器)
		1.第一步:先把parse(“要搜索的词句”)中传入的“要搜索的词句”进行分词。
		2.第二步:把每个分好的词分别依次赋值给“title”字段名然后根据索引进行查询。 

	2.MultiFieldQueryParser(多字段的查询解析器)
 		1.第一步:先把parse(“要搜索的词句”)中传入的“要搜索的词句”进行分词
		2.第二步:把每个分好的词分别依次赋值给指定的“id”、“title”进行查询,
	  		  即同一个分词会分别赋值给“id”和“title”进行查询。
		3.第三步:查询出的数据为并集:把A集合 与 B集合 合并在一起组成的新集合。

		//第一种查询解析器:查询单字段的查询解析器
	 	//查询解析器对象(构造参数:1.搜索的目标字段名  2.使用何种分词器对搜索的参数进行分析)
		//此处使用中文分词器IKAnalyzer 对"content"字段数据进行搜索
		QueryParser queryParser = new QueryParser("content", new IKAnalyzer());
		//第二种查询解析器:查询多字段的查询解析器。会把"要搜索的词句中的"每个分词赋值给多个"搜索的目标字段名"进行搜索
		//MultiFieldQueryParser queryParser = new MultiFieldQueryParser(new String[]{"id","content"}, new IKAnalyzer());
		//查询对象,包含要查询的关键词信息。首先会先对"要搜索的词句"进行分词,然后再把每个分词赋值给单个/多个"搜索的目标字段名"
		Query query = queryParser.parse("要搜索的词句");

2.查询对象
	1.Query(查询对象,包含要查询的关键词信息)
		通过QueryParser(单一字段的查询解析器)解析关键字,得到查询对象。
		1.第一步:先把parse(“要搜索的词句”)中传入的“要搜索的词句”进行分词。
		2.第二步:把每个分好的词分别依次赋值给“title”字段名然后根据索引进行查询。


	2.自定义查询对象(特殊查询)
		1.我们可以通过Query的子类,直接创建查询对象,实现高级查询。
		2.Query的子类如下:

		3.举例部分的Query的子类:
			1.TermQuery:直接对词条进行等值查询
			2.WildcardQuery:不分词直接对词条进行通配符查询
				“?”:匹配一个任意字符 
				“*”:匹配多个任意字符
			3.FuzzyQuery:不分词直接对词条进行容错查询
			4.NumericRangeQuery:数值范围查询
			5.BooleanQuery:多个query组合到一起搜索 
			6.MatchAllDocsQuery:查询所有的文档
 
3.IndexSearcher(索引搜索对象,执行搜索功能)
	1.IndexSearcher:快速搜索、排序等功能。IndexSearcher需要依赖IndexReader类。
		// 索引库位置:"c:\\index"
		Directory directory = FSDirectory.open(new File("c:\\index"));
		// 索引读取工具
		DirectoryReader reader = DirectoryReader.open(directory);
		// 索引搜索工具
		IndexSearcher indexSearcher = new IndexSearcher(reader);

	2.查询后得到的结果,就是打分排序后的前N名结果。N可以通过第2个参数来指定:

4.TopDocs(查询结果对象)
	1.通过索引搜索工具IndexSearcher可以搜索到结果:TopDocs对象	
		在TopDocs对象中,包含两部分信息:
			int totalHits:查询到的数据库中匹配到的的总条数
			ScoreDoc[] scoreDocs:把“指定要获取的匹配度最高的前面n条文档内容信息”封装到数组中 

		//使用索引搜索工具IndexSearcher 根据Query查询对象查询获取匹配度最高的前面n条文档内容信息
		//TopDocs topDocs = indexSearcher.search(Query查询对象, 获得匹配度最高的前面n条文档内容信息) 
		TopDocs topDocs = indexSearcher.search(query, 10) 
		//返回值TopDocs查询结果对象中封装了两部分数据:	
		//	1.TopDocs中的第一部分数据:
		//		int totalHits = topDocs.totalHits
		//		查询到的数据库中匹配到的的总条数
		//	2.TopDocs中的第二部分数据:
		//		ScoreDoc[] scoreDocs = topDocs.scoreDocs
		//		ScoreDoc数组中封装了匹配度最高的前面n条文档内容信息。

		//totalHits:查询获取出来的到的总条数
		int totalHits = topDocs.totalHits;
		//ScoreDoc[]数组中的每个ScoreDoc对象 封装了两部分数据:
		//	1.ScoreDoc对象中第一部分数据:float score = scoreDoc.score:文档得分
		//				      文档得分越高表示匹配度越高,文档得分越低表示匹配度越低。
		//	2.ScoreDoc对象中第二部分数据:int docId = scoreDoc.doc:文档编号
		ScoreDoc[] scoreDocs = topDocs.scoreDocs;
 
5.ScoreDoc(得分文档对象)
	ScoreDoc是得分文档对象,包含两部分数据:
		int doc:文档的编号。拿到文档编号后,然后可以使用使用索引搜索工具IndexSearcher根据文档编号 找到对应的Document文档对象
		float score:文档的得分(文档得分越高表示匹配度越高,文档得分越低表示匹配度越低)

		//使用索引搜索工具IndexSearcher 根据Query查询对象查询获取匹配度最高的前面n条文档内容信息
		//TopDocs topDocs = indexSearcher.search(Query查询对象, 获得匹配度最高的前面n条文档内容信息) 
		TopDocs topDocs = indexSearcher.search(queryParser.parse("要搜索的词句"), 10) 
		//返回值TopDocs查询结果对象中封装了两部分数据:	
		//	1.TopDocs中的第一部分数据:
		//		int totalHits = topDocs.totalHits
		//		查询到的数据库中匹配到的的总条数
		//	2.TopDocs中的第二部分数据:
		//		ScoreDoc[] scoreDocs = topDocs.scoreDocs
		//		ScoreDoc数组中封装了匹配度最高的前面n条文档内容信息。
		//totalHits:查询获取出来的到的总条数
		int totalHits = topDocs.totalHits;
		//ScoreDoc[]数组中的每个ScoreDoc对象 封装了两部分数据:
		//	1.ScoreDoc对象中第一部分数据:float score = scoreDoc.score:文档得分
		//				      文档得分越高表示匹配度越高,文档得分越低表示匹配度越低。
		//	2.ScoreDoc对象中第二部分数据:int docId = scoreDoc.doc:文档编号
		ScoreDoc[] scoreDocs = topDocs.scoreDocs;
		for (ScoreDoc scoreDoc : scoreDocs) 
		{
			//ScoreDoc对象中第一部分数据:文档得分(ScoreDoc对象名.score)
			float score = scoreDoc.score;
			//ScoreDoc对象中第二部分数据:文档编号(ScoreDoc对象名.doc)
			int docId = scoreDoc.doc;
			//根据文档编号获取到Document文档对象:使用索引搜索工具IndexSearcher根据文档编号 找到对应的Document文档对象
			Document document = indexSearcher.doc(docId); 
			//Document文档对象名.get(“字段名”):获取字段值
			System.out.println(document.get("id"));
			System.out.println(document.get("desc"));
			System.out.println(document.get("title"));
		}

示例代码

	// 第一种查询解析器:查询单字段的查询解析器
	// 查询解析器对象(构造参数:1.搜索的目标字段名 2.使用何种分词器对搜索的参数进行分析)
	@Test
	public void searchIndex() throws Exception {

		// 构建查询对象
		QueryParser parser = new QueryParser("desc", new IKAnalyzer());
		Query query = parser.parse("一分钟");

		// 创建索引查询对象
		DirectoryReader reader = DirectoryReader.open(FSDirectory.open(new File("F:\\LucenenData\\indexDir")));
		IndexSearcher indexSearcher = new IndexSearcher(reader);

		// 查询
		TopDocs topDocs = indexSearcher.search(query, 4);

		int totalHits = topDocs.totalHits;
		System.out.println(totalHits);

		ScoreDoc[] scoreDocs = topDocs.scoreDocs;
		for (ScoreDoc scoreDoc : scoreDocs) {
			int docId = scoreDoc.doc;
			Document document = indexSearcher.doc(docId);
			System.out.println(document.get("id"));
			System.out.println(document.get("desc"));
			System.out.println(document.get("title"));
			System.out.println("---------------");
			float score = scoreDoc.score;
			System.out.println();
		}
		indexSearcher.getIndexReader().close();

	}

	// 第二种查询解析器:查询多字段的查询解析器。会把"要搜索的词句中的"每个分词赋值给多个"搜索的目标字段名"进行搜索
	@Test
	public void searchIndex2() throws Exception {

		// 构建查询对象
		MultiFieldQueryParser parser = new MultiFieldQueryParser(new String[] { "title", "desc" }, new IKAnalyzer());
		Query query = parser.parse("小米 雷军");

		// 创建索引查询对象
		DirectoryReader reader = DirectoryReader.open(FSDirectory.open(new File("F:\\LucenenData\\indexDir")));
		IndexSearcher indexSearcher = new IndexSearcher(reader);

		// 查询
		TopDocs topDocs = indexSearcher.search(query, 10);
		ScoreDoc[] scoreDocs = topDocs.scoreDocs;
		for (ScoreDoc scoreDoc : scoreDocs) {
			int docId = scoreDoc.doc;
			Document document = indexSearcher.doc(docId);
			System.out.println(document.get("title"));
			System.out.println(document.get("desc"));
		}
		indexSearcher.getIndexReader().close();
	}


	@Test
	public void searchIndex() throws IOException, ParseException{
		
		//查询解析器对象  构造参数:1.搜索的目标字段名称   2.使用何种分词器对搜索的参数进行分析
//		QueryParser queryParser = new QueryParser("content", new IKAnalyzer());
		//同时查询多字段的查询解析器
		MultiFieldQueryParser parser = new MultiFieldQueryParser(new String[]{"id","content"}, new IKAnalyzer());
		// 对搜索的参数进行解析  解析后得到Query对象
		Query query = parser.parse("linux");
		DirectoryReader reader = DirectoryReader.open(FSDirectory.open(new File("F:\\LucenenData\\indexDir")));
		//创建索引查询对象
		IndexSearcher indexSearcher = new IndexSearcher(reader);
		//topDocs:排名前 n 的结果集
		TopDocs topDocs = indexSearcher.search(query, Integer.MAX_VALUE);
		//得分文档集合
		ScoreDoc[] scoreDocs = topDocs.scoreDocs;
		
		for(ScoreDoc sd : scoreDocs){
			Integer docID = sd.doc;
			Document document = indexSearcher.doc(docID);
			System.out.println(sd.score);
			System.out.println("搜索到的结果集id = " + document.get("id"));
			System.out.println("搜索到的结果集title = " + document.get("title"));
			System.out.println("搜索到的结果集content = " + document.get("content"));
		}
		indexSearcher.getIndexReader().close();
	}


多域查询


查询所有的文档、TermQuery词条查询、WildcardQuery通配符查询、FuzzyQuery模糊查询/容错查询、NumericRangeQuery数值范围查询、BooleanQuery组合查询

1.抽取公用的搜索方法:
	public void search(Query query) throws Exception 
	{
		// 创建目录对象
		Directory directory = FSDirectory.open(new File("C:\\tmp\\indexDir"));
		// 索引的读取对象
		IndexReader indexReader = DirectoryReader.open(directory);
		// 索引的搜索工具
		IndexSearcher searcher = new IndexSearcher(indexReader);
		// search:1.参数1:query查询对象,2.参数2:查询的条数 3.返回的是指定查询条数的文档数据。
		// TopDocs对象:包含所匹配到的文档的总条数,和包含指定查询条数的文档编号的数组
		TopDocs topDocs = searcher.search(query, 10);
		System.out.println("搜索的命中总条数:" + topDocs.totalHits);
		// 获取得分文档的数组,得分文档包含文档编号以及得分
		ScoreDoc[] scoreDocs = topDocs.scoreDocs;
		for (ScoreDoc scoreDoc : scoreDocs) 
		{
			System.out.println("文档编号" + scoreDoc.doc);
			System.out.println("文档得分" + scoreDoc.score);
			// 根据编号查询文档
			Document document = indexReader.document(scoreDoc.doc);
			System.out.println(document.get("id"));
			System.out.println(document.get("title"));
		}
	}

2.MatchAllDocsQuery(查询所有的文档)
	@Test
	public void matchAllDocsQuery() 
	{		
		//查询所有的文档对象数据
		Query query = new MatchAllDocsQuery();
		baseSearch(query);

	}

3.TermQuery(词条查询)
	//TermQuery(词条查询)
	//1.查询内容不会进行分词,查询内容直接就作为一个词条。查询条件必须是最小粒度不可再分割的内容
	//2.查询内容作为一个整体,也即作为一个词条,进行等值匹配,即完全匹配,查询内容(词条)必须和库中的分词完全匹配。
	//场景:不可分割的字段可以采用,比如id
	//缺点:只能查询一个词,例如可以查询"谷歌",不能查询"谷歌地图"
	@Test
	public void testTermSearcher() throws IOException, ParseException
	{
		// 创建查询对象
		Query query = new TermQuery(new Term("title", "谷歌"));
		// 执行搜索操作
		searcher(query);
	}

	//TermQuery(词条查询):直接对词条进行等值查询
	@Test
	public void termQuery() {
		Term term = new Term("desc", "三星炸了");
		Query query = new TermQuery(term);
		baseSearch(query);
	}

	//TermQuery(词条查询):单个词条的搜索,输入的内容会被当做一个完整的词条,不会再对搜索参数进行分词
	public void termQuery()
	{
		//Term term = new Term("查询的目标字段", "查询的参数");
		Term term = new Term("content", "程序");
		Query query = new TermQuery(term);
		try {
			baseSearch(query);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

4.WildcardQuery(通配符查询)
	WildcardQuery(通配符查询):
		1.“?”表示匹配一个中文字符
		2.“*”表示匹配任意多个中文字符

	@Test
	public void testWildCardQuery() throws Exception 
	{
		// 查询条件对象(通配符):
		// 	?:通配一个字符
		// 	*:通配多个字符
		Query query = new WildcardQuery(new Term("title", "*歌*"));
		search(query);
	}

	//WildcardQuery(通配符查询):不分词直接对词条进行通配符查询 
	//?:匹配一个任意字符。	*:匹配多个任意字符
	@Test
	public void wildcardQuery() 
	{
		Term term = new Term("desc", "电*");
		Query query = new WildcardQuery(term);
		baseSearch(query);
	}

5.FuzzyQuery(模糊查询/容错查询) 
	1.不显式写出“FuzzyQuery(Term对象, 容错两次)”:默认允许容错两次,实际指的是最大编辑次数(纠错次数、补位次数、移动位置的次数)
	2.显式写出“FuzzyQuery(Term对象, 容错两次)”:
		比如库中的词条是Itcast,而我要搜索的内容是Itacts,那么通过移动字母位置的方式,a和c移位,t和s移位,一共两次移动位置,照样可以配制出库中的词条Itcast。

	@Test
	public void testFuzzyQuery() throws Exception 
	{
		// 查询条件对象(模糊查询/容错查询)
		// 参数:1.词条,查询字段及关键词,关键词允许写错;2.允许写错的最大编辑距离,并且不能大于2(0~2)。自动补齐或切换位置,至多两次机会
		// 最大编辑距离:facebool-->facebook需要编辑的次数1次,包括大小写
		Query query = new FuzzyQuery(new Term("title", "facebook"), 1);
		search(query);
	}

	//FuzzyQuery(模糊查询/容错查询):不分词直接对词条进行容错查询
	@Test
	public void fuzzyQuery() 
	{
		Term term = new Term("title", "iyhony");
		Query query = new FuzzyQuery(term, 2);
		baseSearch(query);
	}

6.NumericRangeQuery(数值范围查询)
	@Test
	public void testNumericRangeQuery() throws Exception 	
	{
		// 查询条件对象(数值范围查询)
		// 查询非String类型的数据或者说是一些继承Numeric类的对象的查询
		// 1.字段;2.最小值;3.最大值;4.是否包含最小值自身;5.是否包含最大值自身;
		// NumericRangeQuery.newLongRange("搜索的目标字段", 起始范围, 结束范围, 是否包含最小, 是否包含最大);
		Query query = NumericRangeQuery.newLongRange("id", 2l, 4l, true, true);
		search(query);
	}

	//NumericRangeQuery(数值范围查询)
	@Test
	public void numericRangeQuery() 	
	{
		// NumericRangeQuery.newLongRange("搜索的目标字段", 起始范围, 结束范围, 是否包含最小, 是否包含最大);
		Query query = NumericRangeQuery.newDoubleRange("price", 50.88, 9999.00, true, true);
		baseSearch(query);
	}

7.BooleanQuery(组合查询)
		// 交集(两个集合的相同部分):Occur.MUST + Occur.MUST
		// 并集(两个集合合并为一个整体新的集合):Occur.SHOULD + Occur.SHOULD
		// 差集(集合A 减 集合B;包含集合A 但不包含B 所构成的集合):Occur.MUST + Occur.MUST_NOT
		// 非(不包含该集合):Occur.MUST_NOT
		// 包含(包含该集合):Occur.MUST

	@Test
	public void testBooleanQuery() throws Exception 
	{
		Query query1 = NumericRangeQuery.newLongRange("id", 2l, 4l, true, true);//NumericRangeQuery(数值范围查询)
		Query query2 = NumericRangeQuery.newLongRange("id", 0l, 3l, true, true);//NumericRangeQuery(数值范围查询)
		
		// boolean查询本身没有查询条件,它可以组合其他查询
		BooleanQuery query = new BooleanQuery();
 
		//求 query1 和 query2 之间的 并集
		query.add(query1, Occur.SHOULD);
		query.add(query2, Occur.SHOULD);
		
		search(query);
	}

	@Test
	public void booleanQueryTest() 
	{
		BooleanQuery query = new BooleanQuery();

		// 交集: A结果集must(必须) + B结果集must(必须) = A和B之间共同的部分
		   Term term1 = new Term("title", "高富帅");
		   TermQuery termQuery1 = new TermQuery(term1, 1);  
		   query.add(termQuery1, Occur.MUST);

		   Term term2 = new Term("title", "白富美"); 
		   TermQuery termQuery2 = new TermQuery(term2); 
		   query.add(termQuery2, Occur.MUST);

		   baseSearch(query);

		//-------------------------------------------------------
		// 并集: should + should = A和B的结果集合并
		   Term term1 = new Term("title", "高富帅"); 
		   TermQuery termQuery1 = new TermQuery(term1, 1);
		   query.add(termQuery1, Occur.SHOULD);

		   Term term2 = new Term("title", "白富美"); 
		   TermQuery termQuery2 = new TermQuery(term2); query.add(termQuery2, Occur.SHOULD);

		   baseSearch(query);

		//-------------------------------------------------------
		// 差集(集合A 减 集合B;包含集合A 但不包含B 所构成的集合):排除。MUST+MUST_NOT表示包含前者并且排除后者的集合。
		   Term term1 = new Term("title", "高富帅"); 
		   TermQuery termQuery1 = new TermQuery(term1, 1); 
		   query.add(termQuery1, Occur.MUST);
		   
		   Term term2 = new Term("title", "白富美"); 
		   TermQuery termQuery2 = new TermQuery(term2); 
		   query.add(termQuery2, Occur.MUST_NOT);

		   baseSearch(query);

		// MUST_NOT+MUST_NOT,什么都查询不出来(违规条件)
		// SHOULD+MUST,只查MUST条件的查询结果,SHOULD条件失效(违规条件)
		// SHOULD+MUST_NOT 与 MUST+MUST_NOT 等效 
	}

TermQuery(词条查询)


WildcardQuery(通配符查询)


模糊查询/容错查询

1.不写“FuzzyQuery(Term对象, 容错两次)”:默认允许容错两次,实际指的是最大编辑次数(纠错次数、补位次数、移动位置的次数)
2.显式写出“FuzzyQuery(Term对象, 容错两次)”:
	比如库中的词条是Itcast,而我要搜索的内容是Itacts,那么通过移动字母位置的方式,a和c移位,t和s移位,一共两次移动位置,
	照样可以配制出库中的词条Itcast。


NumericRangeQuery(数值范围查询)


BooleanQuery(组合查询)


修改索引、删除索引

更新索引
	//本质先删除再添加。先删除所有满足条件的文档,再把新的文档对象Document存入进去。
	//因此,更新索引通常要根据唯一字段。
	@Test
	public void testUpdate() throws IOException{
		
		// 创建文档对象
		Document document = new Document();
		document.add(new StringField("id", "9", Store.YES));
		document.add(new TextField("title", "谷歌地图之父跳槽FaceBook", Store.YES));
		
		// 索引库对象
		Directory directory = FSDirectory.open(new File("C:\\tmp\\index"));
		// 索引写入器配置对象
		IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST, new IKAnalyzer());
		// 索引写入器对象
		IndexWriter indexWriter = new IndexWriter(directory, conf);
		
		// 执行更新操作
		indexWriter.updateDocument(new Term("id", "1"), document);
		// 提交
		indexWriter.commit();
		// 关闭
		indexWriter.close();
		
	}

	//修改索引的原理:先删除根据条件查询的所有的结果,然后再添加一个新的文档对象Document
	@Test
	public void updateIndex() throws IOException{
		IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST, new IKAnalyzer());
		IndexWriter indexWriter = new IndexWriter(FSDirectory.open(new File(INDEX_PATH)), conf);
		Term term = new Term("content", "传智播客");
		Document doc = new Document();
		LongField id = new LongField("id", 30L, Store.YES);
		doc.add(id);
		TextField title = new TextField("title", "谷歌地图之父跳槽FaceBook", Store.YES);
		doc.add(title);
		TextField content = new TextField("content", "程序员是未来的社会栋梁", Store.YES);
		doc.add(content);
		// 根据指定的词条进行搜索,所有与词条匹配的内容都会被指定的新文档对象Document所覆盖
		indexWriter.updateDocument(term, doc);
		indexWriter.commit();
		indexWriter.close();
		
	}

删除索引
	@Test
	public void testDelete() throws IOException {

		// 创建目录对象
		Directory directory = FSDirectory.open(new File("C:\\tmp\\indexDir"));
		// 创建索引写入器配置对象
		IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST, new IKAnalyzer());
		// 创建索引写入器对象
		IndexWriter indexWriter = new IndexWriter(directory, conf);

		// 执行删除操作(根据词条),要求id字段必须是字符串类型
		// indexWriter.deleteDocuments(new Term("id", "5"));
		// 根据数值范围删除多个文档对象
		// indexWriter.deleteDocuments(NumericRangeQuery.newLongRange("id", 2l, 4l, true, false));
		// 删除所有
		indexWriter.deleteAll();

		// 提交
		indexWriter.commit();
		// 关闭
		indexWriter.close();
	}
 
	@Test
	public void deleteIndex() throws IOException{
		IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST, new IKAnalyzer());
		IndexWriter indexWriter = new IndexWriter(FSDirectory.open(new File(INDEX_PATH)), conf);
		//根据指定的词条进行删除
 		indexWriter.deleteDocuments(new Term("content","linux"));
		indexWriter.deleteAll();//删除所有
		indexWriter.commit();
		indexWriter.close();
	}

Lucene的高级使用:高亮显示、排序、分页

 

高亮显示

1.高亮显示原理:
	给所有关键字加上一个HTML标签。
	给这个特殊的标签设置CSS样式。

2.pom.xml中配置依赖:
		<!-- lucene的高亮显示 -->
		<dependency>
			<groupId>org.apache.lucene</groupId>
			<artifactId>lucene-highlighter</artifactId>
			<version>4.10.2</version>
		</dependency>


3.示例代码1:
	//高亮查询:高亮就是搜索的参数(关键词高亮显示)
	@Test
	public void searchhighlighter() throws IOException, ParseException, InvalidTokenOffsetsException{
		
		//查询解析器对象  构造参数:1.搜索的目标字段名称   2.使用何种分词器对搜索的参数进行分析
		//QueryParser queryParser = new QueryParser("content", new IKAnalyzer());
		//同时查询多字段的查询解析器
		MultiFieldQueryParser parser = new MultiFieldQueryParser(new String[]{"id","content"}, new IKAnalyzer());
		// 对搜索的参数进行解析  解析后得到Query对象
		Query query = parser.parse("传智播客在哪里?");
		
		DirectoryReader reader = DirectoryReader.open(FSDirectory.open(new File(INDEX_PATH)));
		//创建索引查询对象
		IndexSearcher indexSearcher = new IndexSearcher(reader);
		
		//第一个参数:高亮显示的文字的前缀。第二个参数:高亮显示的文字的后缀。
		//<em color='red'>高亮显示的文字</em> 
		Formatter formatter = new SimpleHTMLFormatter("<em color='red'>", "</em>");
		//把Query查询对象传入,最终目的用于高亮显示分词
		Scorer fragmentScorer = new QueryScorer(query);
		//创建高亮显示处理对象
		Highlighter highlighter = new Highlighter(formatter, fragmentScorer);
		
		//topDocs:排名前 n 的结果集
		TopDocs topDocs = indexSearcher.search(query, Integer.MAX_VALUE);
		//得分文档集合
		ScoreDoc[] scoreDocs = topDocs.scoreDocs;
		
		for(ScoreDoc sd : scoreDocs){
			Integer docID = sd.doc;
			Document document = indexSearcher.doc(docID);
			System.out.println(sd.score);
			System.out.println("搜索到的结果集id = " + document.get("id"));
			System.out.println("搜索到的结果集title = " + document.get("title"));
			String content = document.get("content");
			//对结果集进行高亮处理:只会对content字段数据中的匹配到的分词进行高亮显示
			String highlighterContent = highlighter.getBestFragment(new IKAnalyzer(), "content", content);
			System.out.println("搜索到的结果集content = " + highlighterContent);
		}
		indexSearcher.getIndexReader().close();
	}

4.示例代码2:
	// 高亮显示
	@Test
	public void testHighlighter() throws Exception {
		// 目录对象
		Directory directory = FSDirectory.open(new File("indexDir"));
		// 创建读取工具
		IndexReader reader = DirectoryReader.open(directory);
		// 创建搜索工具
		IndexSearcher searcher = new IndexSearcher(reader);

		QueryParser parser = new QueryParser("title", new IKAnalyzer());
		Query query = parser.parse("谷歌地图");
		
		// 格式化器
		Formatter formatter = new SimpleHTMLFormatter("<em>", "</em>");
		Scorer scorer = new QueryScorer(query);
		// 准备高亮工具
		Highlighter highlighter = new Highlighter(formatter, scorer);
		// 搜索
		TopDocs topDocs = searcher.search(query, 10);
		System.out.println("本次搜索共" + topDocs.totalHits + "条数据");
		
		ScoreDoc[] scoreDocs = topDocs.scoreDocs;
		for (ScoreDoc scoreDoc : scoreDocs) {
			// 获取文档编号
			int docID = scoreDoc.doc;
			Document doc = reader.document(docID);
			System.out.println("id: " + doc.get("id"));
			
			String title = doc.get("title");
			// 用高亮工具处理普通的查询结果,参数:分词器,要高亮的字段的名称,高亮字段的原始值
			String hTitle = highlighter.getBestFragment(new IKAnalyzer(), "title", title);
			
			System.out.println("title: " + hTitle);
			// 获取文档的得分
			System.out.println("得分:" + scoreDoc.score);
		}

	}

排序

1.示例代码1:
	//对搜索的结果集进行排序
     	public void sortSearch() throws IOException, ParseException{
		
		DirectoryReader reader = DirectoryReader.open(FSDirectory.open(new File(INDEX_PATH)));
		//创建索引查询对象
		IndexSearcher indexSearcher = new IndexSearcher(reader);
		
		org.apache.lucene.queryparser.classic.QueryParser parser = new org.apache.lucene.queryparser.classic.QueryParser("content", new IKAnalyzer());
		Query query = parser.parse("linux的优秀的操作系统");
		 
		//SortField:
		// 	第一个参数:指定排序字段
		// 	第二个参数:指定字段类型
		// 	第三个参数:指定使用的排序规则:默认为 false(升序)。可另外修改指定为 true(降序)
		Sort sort = new Sort(new SortField("id", Type.LONG,true));
		
		//topDocs:排名前 n 的结果集
		TopDocs topDocs = indexSearcher.search(query, 30, sort);
		//得分文档集合
		ScoreDoc[] scoreDocs = topDocs.scoreDocs;
		
		for(ScoreDoc sd : scoreDocs){
			Integer docID = sd.doc;
			Document document = indexSearcher.doc(docID);
			System.out.println("文档的得分:"+sd.score);
			System.out.println("搜索到的结果集id = " + document.get("id"));
			System.out.println("搜索到的结果集title = " + document.get("title"));
			System.out.println("搜索到的结果集content = " + document.get("content"));
		}
		indexSearcher.getIndexReader().close();
	}

2.示例代码2:
	// 排序
	@Test
	public void testSortQuery() throws Exception {
		// 目录对象
		Directory directory = FSDirectory.open(new File("indexDir"));
		// 创建读取工具
		IndexReader reader = DirectoryReader.open(directory);
		// 创建搜索工具
		IndexSearcher searcher = new IndexSearcher(reader);
		
		QueryParser parser = new QueryParser("title", new IKAnalyzer());
		Query query = parser.parse("谷歌地图");
		
		// 创建排序对象,需要使用排序字段SortField。
		//参数:字段的名称。字段的类型。默认false(升序),可使用true(降序)。
		Sort sort = new Sort(new SortField("id", Type.LONG, true));
		// 搜索
		TopDocs topDocs = searcher.search(query, 10, sort);
		System.out.println("本次搜索共" + topDocs.totalHits + "条数据");
		
		ScoreDoc[] scoreDocs = topDocs.scoreDocs;
		for (ScoreDoc scoreDoc : scoreDocs) {
			// 获取文档编号
			int docID = scoreDoc.doc;
			Document doc = reader.document(docID);
			System.out.println("id: " + doc.get("id"));
			System.out.println("title: " + doc.get("title"));
		}
	}

分页

1.分页分两种:
	1.物理分页:limit
	2.逻辑分页:代码

2.示例代码1:
	//分页查询,并根据id降序排列
	@Test
     	public void pageSortSearch() throws IOException, ParseException{
		int pageNum = 3;//页码
		int pageSize = 10;//页面数据数量
		int start = (pageNum -1) * pageSize;//每页的起始位置
		int end = pageNum * pageSize;//每页的结束位置

		DirectoryReader reader = DirectoryReader.open(FSDirectory.open(new File(INDEX_PATH)));
		//创建索引查询对象
		IndexSearcher indexSearcher = new IndexSearcher(reader);
		
		QueryParser parser = new QueryParser("content", new IKAnalyzer());
		Query query = parser.parse("linux的优秀的操作系统");
		// 创建排序对象,需要使用排序字段SortField。
		//参数:字段的名称。字段的类型。默认false(升序),可使用true(降序)。
		Sort sort = new Sort(new SortField("id", Type.LONG,true));
		
		//topDocs:获取匹配到的前end条匹配度最高的数据,查询0~end条。
		TopDocs topDocs = indexSearcher.search(query, end, sort);
		//得分文档集合
		ScoreDoc[] scoreDocs = topDocs.scoreDocs;
		//start:每页的起始位置
		for(int i=start ; i<scoreDocs.length;i++)
		{
			ScoreDoc sd = scoreDocs[i];
			Integer docID = sd.doc;
			Document document = indexSearcher.doc(docID);
			System.out.println("文档的得分:"+sd.score);
			System.out.println("搜索到的结果集id = " + document.get("id"));
			System.out.println("搜索到的结果集title = " + document.get("title"));
			System.out.println("搜索到的结果集content = " + document.get("content"));
		}
		indexSearcher.getIndexReader().close();
	}

3.示例代码2:
	// 分页
	@Test
	public void testPageQuery() throws Exception {
		// 实际上Lucene本身不支持分页。因此我们需要自己进行逻辑分页。我们要准备分页参数:
		int pageSize = 2;// 每页条数
		int pageNum = 3;// 当前页码
		int start = (pageNum - 1) * pageSize;// 当前页的起始条数
		int end = start + pageSize;// 当前页的结束条数(不能包含)
		
		// 目录对象
		Directory directory = FSDirectory.open(new File("indexDir"));
		// 创建读取工具
		IndexReader reader = DirectoryReader.open(directory);
		// 创建搜索工具
		IndexSearcher searcher = new IndexSearcher(reader);
		
		QueryParser parser = new QueryParser("title", new IKAnalyzer());
		Query query = parser.parse("谷歌地图");
		
		// 创建排序对象,需要使用排序字段SortField。
		//参数:字段的名称。字段的类型。默认false(升序),可使用true(降序)。
		Sort sort = new Sort(new SortField("id", Type.LONG, false));

		// 搜索数据,查询0~end条
		TopDocs topDocs = searcher.search(query, end, sort);
		System.out.println("本次搜索共" + topDocs.totalHits + "条数据");
		
		ScoreDoc[] scoreDocs = topDocs.scoreDocs;
		//start:每页的起始位置
		for (int i = start; i < end; i++) {
			ScoreDoc scoreDoc = scoreDocs[i];
			// 获取文档编号
			int docID = scoreDoc.doc;
			Document doc = reader.document(docID);
			System.out.println("id: " + doc.get("id"));
			System.out.println("title: " + doc.get("title"));
		}
	}

 

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

あずにゃん

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值