Solr底层Api:Lucene

什么是lucene?

logo:


1)Lucene是一套用于全文检索和搜寻的开源程序库,由Apache软件基金会支持和提供
2)Lucene提供了一个简单却强大的应用程序接口(API),能够做全文索引和搜寻,在Java开发环境里Lucene是一个成熟的免费开放源代码工具
3)Lucene并不是现成的搜索引擎产品,但可以用来制作搜索引擎产品

4)官网:http://lucene.apache.org/


什么是全文检索?



lucene和solr的关系



实现搜索的方式:

方式1:数据库搜索

利用SQL语句进行模糊搜索:
select * from items where title like “%小米%”;
问题:
数据量很大的情况下,模糊搜索不一定走索引,因此效率就会很低。


方式2:Lucene技术

海量数据的情况下,利用倒排索引技术,实现快速的搜索、打分、排序等功能 


倒排索引技术:



创建倒排索引,分为以下几步:
1)创建文档列表:

             lucene首先对原始文档数据进行编号(DocID),形成列表,就是一个文档列表


2)创建倒排索引列表:

              然后对文档中数据进行分词,得到词条。对词条进行编号,以词条创建索引。然后记录下包含该词条的所有文档编号(及其它信息)。

江湖悠悠 -->江湖、悠悠


倒排索引创建索引的流程:
1)首先把所有的原始数据进行编号,形成文档列表

2)把文档数据进行分词,得到很多的词条,以词条为索引。保存包含这些词条的文档的编号信息

搜索的过程:
当用户输入任意的词条时,首先对用户输入的数据进行分词,得到用户要搜索的所有词条,然后拿着这些词条去倒排索引列表中进行匹配。找到这些词条就能找到包含这些词条的所有文档的编号。然后根据这些编号去文档列表中找到文档

例如用户在百度中搜索"江湖"关键字,lucene根据这个单词,找到倒排列表中的文档id(0,1) ,然后根据文档id(0,1)查找出"j江湖悠悠,饮一壶浊酒"和“行走江湖,靠的是颜值”这两条数据展示给用户


Lucene的基本使用

使用Lucene的API来实现对索引的增(创建索引)、删(删除索引)、改(修改索引)、查(搜索数据)。

一:导入依赖

<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-highlighter</artifactId>
	<version>4.10.2</version>
</dependency>
<!-- IK分词器 -->
<dependency>
	<groupId>com.janeluo</groupId>
	<artifactId>ikanalyzer</artifactId>
	<version>2012_u6</version>
</dependency>
</dependencies>

添加:

package LuceneTest;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

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.index.IndexWriterConfig.OpenMode;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
import org.junit.Test;
import org.wltea.analyzer.lucene.IKAnalyzer;

public class LuceneCreate {

	/*
	 * 创建文档(原始数据)索引
	 */
	@Test
	public void createIndex() throws Exception {

		// 创建文档对象
		Document document = new Document();
		// 创建并添加字段信息。参数:字段的名称、字段的值、是否存储,这里选Store.YES代表存储到文档列表。Store.NO代表不存储
		document.add(new StringField("id", "1", Store.YES));
		// 这里我们title字段需要用TextField,即创建索引又会被分词。StringField会创建索引,但是不会被分词
		document.add(new TextField("title", "今夜漫长,江湖悠悠,孤饮一杯浊酒", Store.YES));

		// 索引目录类,保存到项目下indexDir目录
		Directory directory = FSDirectory.open(new File("indexDir"));
		// 创建分词器对象
		IKAnalyzer analyzer = new IKAnalyzer();
		// 索引写出工具的配置对象
		IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST, analyzer);
		// 创建索引的写出工具类。参数:索引的目录和配置信息
		IndexWriter indexWriter = new IndexWriter(directory, conf);

		// 把文档交给IndexWriter
		indexWriter.addDocument(document);
		// 提交
		indexWriter.commit();
		// 关闭
		indexWriter.close();

	}

	/**
	 * 批量创建文档索引
	 * 
	 * @param @throws Exception
	 * @throws
	 */
	@Test
	public void createBatchIndex() throws Exception {

		// 创建文档的集合
		List<Document> docs = new ArrayList<Document>();
		// 创建文档对象
		Document document1 = new Document();
		document1.add(new StringField("id", "1", Store.YES));
		document1.add(new TextField("title", "今夜漫长,江湖悠悠,孤饮一杯浊酒", Store.YES));
		docs.add(document1);
		// 创建文档对象
		Document document2 = new Document();
		document2.add(new StringField("id", "2", Store.YES));
		document2.add(new TextField("title", "行走江湖,靠的就是颜值", Store.YES));
		docs.add(document2);

		// 索引目录类,指定索引在硬盘中的位置
		Directory directory = FSDirectory.open(new File("indexDir"));
		// 引入IK分词器
		IKAnalyzer analyzer = new IKAnalyzer();
		// 索引写出工具的配置对象
		IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST, analyzer);
		// 设置打开方式:OpenMode.APPEND 会在索引库的基础上追加新索引。OpenMode.CREATE会先清空原来数据,再提交新的索引
		conf.setOpenMode(OpenMode.CREATE);

		// 创建索引的写出工具类。参数:索引的目录和配置信息
		IndexWriter indexWriter = new IndexWriter(directory, conf);
		// 把文档集合交给IndexWriter
		indexWriter.addDocuments(docs);
		// 提交
		indexWriter.commit();
		// 关闭
		indexWriter.close();

	}
}

修改:

package LuceneTest;

import java.io.File;

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.index.Term;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
import org.junit.Test;
import org.wltea.analyzer.lucene.IKAnalyzer;

public class LuceneUpdate {
	/*
	 * 测试:修改索引 注意: A:Lucene修改功能底层会先删除,再把新的文档添加。
	 * B:修改功能会根据Term进行匹配,所有匹配到的都会被删除。这样不好 C:因此,一般我们修改时,都会根据一个唯一不重复字段进行匹配修改。例如ID
	 * D:但是词条搜索,要求ID必须是字符串。如果不是,这个方法就不能用。
	 * 如果ID是数值类型,我们不能直接去修改。可以先手动删除deleteDocuments(数值范围查询锁定ID),再添加。
	 */
	@Test
	public void updateIndex() throws Exception {
		// 创建目录对象
		Directory directory = FSDirectory.open(new File("indexDir"));
		// 创建配置对象
		IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST,
				new IKAnalyzer());
		// 创建索引写出工具
		IndexWriter writer = new IndexWriter(directory, conf);

		// 创建新的文档数据
		Document doc = new Document();
		doc.add(new StringField("id", "1", Store.YES));
		doc.add(new TextField("title", "哈哈,追不上我吧,我就是这么强大",
				Store.YES));
		/*
		 * 修改索引。参数: 词条:根据这个词条匹配到的所有文档都会被修改 文档信息:要修改的新的文档数据
		 */
		writer.updateDocument(new Term("id", "1"), doc);
		// 提交
		writer.commit();
		// 关闭
		writer.close();
	}
}

删除:

package LuceneTest;

import java.io.File;

import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.Term;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
import org.junit.Test;
import org.wltea.analyzer.lucene.IKAnalyzer;

public class LuceneDelete {

	/*
	 * 演示:删除索引 注意: 一般,为了进行精确删除,我们会根据唯一字段来删除。比如ID 如果是用Term删除,要求ID也必须是字符串类型!
	 */
	@Test
	public void deleteIndex() throws Exception {
		// 创建目录对象
		Directory directory = FSDirectory.open(new File("indexDir"));
		// 创建配置对象
		IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST,
				new IKAnalyzer());
		// 创建索引写出工具
		IndexWriter writer = new IndexWriter(directory, conf);

		// 根据词条进行删除
		writer.deleteDocuments(new Term("id", "1"));

		// 删除所有
		// writer.deleteAll();

		// 提交
		writer.commit();
		// 关闭
		writer.close();
	}

}

查询:

package LuceneTest;

import java.io.File;

import org.apache.lucene.document.Document;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryparser.classic.MultiFieldQueryParser;
import org.apache.lucene.search.FuzzyQuery;
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.search.WildcardQuery;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.junit.Test;
import org.wltea.analyzer.lucene.IKAnalyzer;

public class LuceneSearch {

	public void search(Query query) throws Exception {

		// 索引目录对象
		Directory directory = FSDirectory.open(new File("indexDir"));
		// 索引读取工具
		IndexReader reader = DirectoryReader.open(directory);
		// 索引搜索工具
		IndexSearcher searcher = new IndexSearcher(reader);

		// 搜索数据,两个参数:查询条件对象要查询的最大结果条数
		// 返回的结果是 按照匹配度排名得分前N名的文档信息(包含查询到的总条数信息、所有符合条件的文档的编号信息)。
		TopDocs topDocs = searcher.search(query, 10);
		// 获取总条数
		System.out.println("本次搜索共找到" + topDocs.totalHits + "条数据");
		// 获取得分文档对象(ScoreDoc)数组.SocreDoc中包含:文档的编号、文档的得分
		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"));
			// 取出文档得分
			System.out.println("得分: " + scoreDoc.score);
		}
	}

	/**
	 * 根据多个字段查询
	 * 
	 */
	@Test
	public void search1() throws Exception {
		MultiFieldQueryParser parser = new MultiFieldQueryParser(new String[] {
				"id", "title" }, new IKAnalyzer());
		//Query query = parser.parse("1");//自动匹配id进行查询
		Query query = parser.parse("浊酒");//自动匹配title进行查询
		search(query);
	}

	/**
	 * 普通词条查询
	 * 
	 */
	@Test
	public void search2() throws Exception {
		// 创建词条查询对象
		Query query = new TermQuery(new Term("title", "浊酒"));
		search(query);
	}
	
	/**
	 * 模糊查询
	 * 	? 可以代表任意一个字符
	 * 	* 可以任意多个任意字符
	 */
	@Test
	public void search3() throws Exception {
		// 创建查询对象
		Query query = new WildcardQuery(new Term("title", "*酒*"));
		search(query);
	}
	
	/**
	 * 测试相似度查询
	 */
	@Test
	public void testFuzzyQuery() throws Exception {
		//如果不采用FuzzyQuery,根据“烈酒”词条是查询不出结果的
 		Query query = new FuzzyQuery(new Term("title","烈酒"));
		search(query);
	}

}

高亮显示关键字:

原理:

1)给所有关键字加上一个HTML标签

        

      2)给这个特殊的标签设置CSS样式

       

代码实现:

// 高亮显示
	@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);
		}

	}


猜你喜欢

转载自blog.csdn.net/qq_37936542/article/details/80306253