- 搜索引擎:
* 什么是搜索引擎
* 搜索引擎基本运行原理
* 原始数据库做搜索有什么弊端
* 倒排索引(敲黑板) - lucene
- lucene相关的概念
- lucene和solr的关系
- lucene入门程序(写入索引的操作代码)
- lucene相关写入索引的api的解释
- ik分词器
- lucene的搜索: 基础的搜索(2个), 多样化的搜索(5个)
- lucene的高级(理解)
- lucene的高亮
- lucene的分页
- lucene的排序
- lucene的激励因子
1. 搜索引擎
1.1 什么是搜索引擎
搜索引擎是指根据一定的策略、运用特定的计算机程序从互联网上搜集信息,在对信息进行组织和处理后,为用户提供检索服务,将用户检索相关的信息展示给用户的系统。搜索引擎包括全文索引、目录索引、元搜索引擎、垂直搜索引擎、集合式搜索引擎、门户搜索引擎与免费链接列表等。
代表性搜索引擎: 谷歌(Google) 百度(baidu)
1.2 搜索引擎的运行原理
- 爬虫程序–>爬取网页–将爬取到的网页保存到临时库中
- 利用规则将数据网页数据处理并保存到索引库中
- 调用查询索引的程序,从索引库中查询相关信息
1.3 原始数据库做搜索的弊端
-
- 慢, 当数据库中保存了大量的数据以后, 查询的效率非常的低
-
- 如果使用的原始的sql完成搜索, 那么只能匹配首尾的内容,和用户的结果有可能是不同的
-
- 如果用户真的输错了, 难道就啥也搜不到了吗? 如果用户输入错误, 那么可以导致其结果和用户想的完全不同
1.4 倒排索引
倒排索引 . 称为反向索引, 可以将一句话, 一个词, 一段话把他进行拆分(分词). 将分好词的数据建立索引, 将其保存的索引库中, 这样当用户来访问的索引库的时候, 将用户输入的内容也进行分词, 将分好词的内容到索引库中查询, 将查询到的内容返回用户
为什么说倒排索引可以提高查询的效率?
假设有一个用户: 输入"谷歌跳槽离开", 先将用户这个输入的内容进行一个分词, (谷歌, 跳槽, 离开),然后将这个三个词语到索引库中进行分开查询, 然后将三个的词语查询的id值返回, 1,2,3,4,5,根据id查询数据库
2. lucene
lucene 是Apache提供的全文检索的工具包, 其本质就是一个工具包, 只不过可以使用lucene帮助我们构建一个全文搜索的引擎
官方网址: http://lucene.apache.org/
2.1 lucene和solr的关系
lucene: 是一个底层的API, 本质是一个工具包
solr: 是一个企业级的搜索引擎, 其底层就是lucene
2.2 lucene的基本的入门
- 第一步: 导包
<!--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-queries</artifactId>
<version>4.10.2</version>
</dependency>
<!--lucene的 测试的先关的, 可以省略的-->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-test-framework</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-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>
- 编写代码(写入索引的代码)
public class IndexWriterLucene {
//演示: 写入索引的操作
public static void main(String[] args) throws Exception {
//1. 需要创建写入索引器对象
//1.1 指定索引库的目录
//此处这个一定要是一个目录, 千万别写文件
Directory directory = FSDirectory.open(new File("F:\\index"));
//1.2 创建索引写入器的配置对象
//参数1: 指定lucene的版本
//参数2: 指定的分词器,目前采用标准分词器
IndexWriterConfig indexWriterConfig = new IndexWriterConfig(Version.LATEST,new StandardAnalyzer());
IndexWriter indexWriter = new IndexWriter(directory,indexWriterConfig);
//2. 添加文档的操作
//2.1 创建文档对象
Document doc = new Document();
//2.1.1 添加文档的内容
doc.add(new LongField("id",1L, Field.Store.YES));
doc.add(new StringField("title","lucene的简介", Field.Store.YES));
doc.add(new TextField("content","lucene是Apache提供的一个全文检索的工具包, 使用lucene可以构建一个搜索引擎", Field.Store.YES));
indexWriter.addDocument(doc);
//3. 写入索引(提交索引)
indexWriter.commit();
//4. 将索引写入器关闭
indexWriter.close();
}
}
- 写入多条索引数据
//演示: 写入多条索引的操作
public static void main(String[] args) throws Exception {
//1. 创建写入索引器对象
//1.1 指定索引库的目录位置
Directory directory = FSDirectory.open(new File("F:\\index"));
//1.2 创建索引写入器的配置对象
IndexWriterConfig indexWriterConfig = new IndexWriterConfig(Version.LATEST,new IKAnalyzer());
//CREATE: 创建, 不管索引库存在不存在, 都是每次执行重新 新的索引库
//APPEND: 追加, 如果索引库存在, 那么就会追加数据, 如果索引库不存在, 直接报错
//CREATE_OR_APPEND: 创建或者追加, 如果索引库不存在, 那么就是创建, 如果索引库存在, 就是追加
indexWriterConfig.setOpenMode(IndexWriterConfig.OpenMode.CREATE_OR_APPEND);
IndexWriter indexWriter = new IndexWriter(directory,indexWriterConfig);
//2. 添加文档内容
List<Document> list = new ArrayList<Document>();
for(int i = 0 ; i<5 ; i++){
//2.1 创建文档对象
Document doc = new Document();
//2.1.1 添加文档的内容
doc.add(new LongField("id",i, Field.Store.YES));
doc.add(new StringField("title","花花", Field.Store.YES));
doc.add(new TextField("content","自定义词汇"+i, Field.Store.YES));
list.add(doc);
}
indexWriter.addDocuments(list);
//3. 提交文档
indexWriter.commit();
//4. 关闭写入器对象
indexWriter.close();
}
2.3 索引查看工具
- 设置索引库的位置
- 视图窗口的解释
- 文档视图的内容
2.4 索引写入相关的api
-
IndexWriter: 索引写入器对象
其主要的作用, 添加索引, 修改索引和删除索引
- 创建此对象的时候, 需要传入Directory和indexWriterConfig对象
-
Directory: 目录类, 用来指定索引库的目录
- 常用的实现类:
- FSDirectory: 用来指定文件系统的目录, 将索引信息保存到磁盘上
- 优点: 索引可以进行长期保存, 安全系数高
- 缺点: 读取略慢
- RAMDriectory: 内存目录, 将索引库信息存放到内存中
- 优点: 读取速度快
- 缺点: 不安全, 无法长期保存, 关机后就消失了
- FSDirectory: 用来指定文件系统的目录, 将索引信息保存到磁盘上
- 常用的实现类:
-
IndexWriterConfig: 索引写入器的配置类
- 创建此对象, 需要传递Lucene的版本和分词器
- 作用:
- 作用1 : 指定Lucene的版本和需要使用的分词器
- 作用2: 设置Lucene的打开索引库的方式: setOpenMode();
//参数值: APPEND CREATE CREATE_OR_APPEND
/**
* APPEND: 表示追加, 如果索引库存在, 就会向索引库中追加数据, 如果索引库不存在, 直接报错
*
* CREATE: 表示创建, 不管索引库有没有, 每一次都是重新创建一个新的索引库
*
* CREATE_OR_APPEND: 如果索引库有, 就会追加, 如果没有 就会创建索引库
默认值也是 CREATE_OR_APPEND
*/
config.setOpenMode(IndexWriterConfig.OpenMode.CREATE_OR_APPEND);
- Document: 文档
在Lucene中, 每一条数据以文档的形式进行存储, 文档中也有其对应的属性和值, Lucene中一个文档类似数据库的一个表, 表中的字段类似于文档中的字段,只不过这个文档只能保存一条数据
Document看做是一个文件, 文件的属性就是文档的属性, 文件对应属性的值就是文档的属性的值 content
- 一个文档中可以有多个字段, 每一个字段就是一个field对象,不同的文档可以有不同的属性
- 字段也有其对应数据类型, 故Field类也提供了各种数据类型的实现类
Field类 | 数据类型 | Analyzed是否分析 | Indexed是否索引 | Stored是否存储 | 说明 |
---|---|---|---|---|---|
StringField(FieldName, FieldValue,Store.YES)) | 字符串 | N | Y | Y或N | 这个Field用来构建一个字符串Field,但是不会进行分析,会将整个串存储在索引中,比如(订单号,姓名等)是否存储在文档中用Store.YES或Store.NO决定 |
LongField(FieldName, FieldValue,Store.YES) | Long型 | Y | Y | Y或N | 这个Field用来构建一个Long数字型Field,进行分析和索引,比如(价格)是否存储在文档中用Store.YES或Store.NO决定 |
StoredField(FieldName, FieldValue) | 重载方法,支持多种类型 | N | N | Y | 这个Field用来构建不同类型Field不分析,不索引,但要Field存储在文档中 |
TextField(FieldName, FieldValue, Store.NO)或TextField(FieldName, reader) | 字符串或流 | Y | Y | Y或N | 如果是一个Reader, lucene猜测内容比较多,会采用Unstored的策略. |
名称解释:
分析: 是否将字段的值进行分词
索引: 指的是能否被搜索
是否保存: 指的的原始值是否需要保存
如果一个字段中的值可以被分词, 那么必然是支持搜索的
-
Analyzer: 分词器:
用于对文档中的数据进行分词, 其分词的效果取决于分词器的选择, Lucene中根据各个国家制定了各种语言的分词器,对中文有一个ChineseAnalyzer 但是其分词的效果, 是将中文进行一个一个字的分开
针对中文分词一般只能使用第三方的分词词:
2.5 集成IK
说明: ik分词器官方版本并不支持Lucene4.x版本, 有人基本官方版本做了改进, 使其支持Lucene4.x
- 基本使用
导包:
<!-- https://mvnrepository.com/artifact/com.janeluo/ikanalyzer -->
<dependency>
<groupId>com.janeluo</groupId>
<artifactId>ikanalyzer</artifactId>
<version>2012_u6</version>
</dependency>
- ik的高级使用
- 引入ik的扩展词典相关的内容
2.6 基本查询
- 基本入门案例(单字段的解析器)
//单字段的查询解析器
@Test
public void queryParseToLucene() throws Exception {
//1. 需要创建查询索引的对象
IndexReader indexReader = DirectoryReader.open(FSDirectory.open(new File("F:\\index")));
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
//2. 执行查询, 返回结果集
//2.1 封装查询条件
// 注意: 此处指定的分词器一定要和写入索引库的分词器一致
QueryParser queryParser = new QueryParser("content",new IKAnalyzer());
//此处的参数, 其实就是用户的输入的内容
Query query = queryParser.parse("程序员是最厉害的人");
TopDocs topDocs = indexSearcher.search(query, Integer.MAX_VALUE);
int totalHits = topDocs.totalHits;//一共查询到了多少条数据
ScoreDoc[] scoreDocs = topDocs.scoreDocs;// 返回的得分文档的数组
System.out.println("一共返回了"+totalHits+"条数据");
//2.2 遍历得分文档的数组
for (ScoreDoc scoreDoc : scoreDocs) {
float score = scoreDoc.score;//获取文档分数
int docID = scoreDoc.doc; //返回文档的id
Document document = indexSearcher.doc(docID);
String id = document.get("id");
String title = document.get("title");
String content = document.get("content");
System.out.println("得分:"+score+" "+id+" "+title+" "+content);
}
}
- 多字段的查询解析器
//多字段的查询解析器
@Test
public void multiFieldQueryParserToLucene() throws Exception {
//1. 创建查询索引的对象
IndexReader reader =DirectoryReader.open(FSDirectory.open(new File("F:\\index")));
IndexSearcher indexSearcher = new IndexSearcher(reader);
//2. 执行查询
MultiFieldQueryParser queryParser = new MultiFieldQueryParser(new String[]{"title","content"},new IKAnalyzer());
Query query = queryParser.parse("程序员是最厉害的");
TopDocs topDocs = indexSearcher.search(query, Integer.MAX_VALUE);
//2.1 获取结果集中内容
int totalHits = topDocs.totalHits;//共计查询到多少条数据
ScoreDoc[] scoreDocs = topDocs.scoreDocs;//得分文档的数组
//2.2 遍历数组, 获取文档的id值
for (ScoreDoc scoreDoc : scoreDocs) {
float score = scoreDoc.score;//文档的得分
int docID = scoreDoc.doc;//文档的id值
Document document = indexSearcher.doc(docID);
String id = document.get("id");
String title = document.get("title");
String content = document.get("content");
System.out.println("得分:"+score+" "+id+" "+title+" "+content);
}
}
2.7 索引查询的相关api
2.8 多样化查询
2.8.1 词条查询 termQuery
/**
* 词条: 是一个不可在分割的内容, 如果写错了, 或者跟索引库中的词条不匹配, 那么都是查询不出来的
* 词条可以是一个字, 是一个词, 甚至可以是一句话或者一段话
* 主要适用于那些不能够进行分词的字段, 比如: id
* @throws Exception
*/
@Test
public void termQueryToLucene() throws Exception {
//1. 创建查询索引的对象
IndexSearcher indexSearcher = new IndexSearcher(DirectoryReader.open(FSDirectory.open(new File("f:\\index"))));
//2. 执行查询
TermQuery query = new TermQuery(new Term("title","lucene的简介"));
TopDocs topDocs = indexSearcher.search(query, Integer.MAX_VALUE);
//2.1 获取结果集中内容
int totalHits = topDocs.totalHits;//共计查询到多少条数据
ScoreDoc[] scoreDocs = topDocs.scoreDocs;//得分文档的数组
//2.2 遍历数组, 获取文档的id值
for (ScoreDoc scoreDoc : scoreDocs) {
float score = scoreDoc.score;//文档的得分
int docID = scoreDoc.doc;//文档的id值
Document document = indexSearcher.doc(docID);
String id = document.get("id");
String title = document.get("title");
String content = document.get("content");
System.out.println("得分:"+score+" "+id+" "+title+" "+content);
}
}
2.8.2 通配符查询: wildCardQuery
//通配符查询
//?: 表示的是一个占位符
//*: 表示的占位0到多个
@Test
public void wildCardQueryToLucene() throws IOException {
WildcardQuery query = new WildcardQuery(new Term("content","蓝瘦香菇*"));
baseQuery(query);
}
2.8.3 模糊查询 fuzzyQuery
/**
* 模糊查询: 最大的编辑次数 2
* 编辑: 替换, 删除, 修改 只要在2次之内能够还原成词条中的内容就可以被查询到
* 编辑的次数: 0~2之间, 默认就是2
*
* @throws IOException
*/
@Test
public void fuzzyQueryToLucene() throws IOException {
FuzzyQuery query = new FuzzyQuery(new Term("content","lucen"),2);
baseQuery(query);
}
2.8.4 数字范围查询 NumericRangeQuery
//数字范围查询
/**
* 参数1: 查询的字段
* 注意: 此处字段的数据类型必须要和NumericRangeQuery的泛型保持一致
* 参数2: 最小值
* 参数3: 最大值
* 参数4: 是否包含最小值
* 参数5: 是否包含最大值
* @throws IOException
*/
@Test
public void numQueryToLucene() throws IOException {
NumericRangeQuery<Long> numericRangeQuery = NumericRangeQuery.newLongRange("id",0L,3L,false,false);
baseQuery(numericRangeQuery);
}
2.8.5 组合查询: BooleanQuery
//组合查询: BooleanQuery
/**BooleanQuery: 本身自己没有任何的条件的
*
* MUST: 必须, 此条件必须存在的 求交集
* SHOULD: 可以存在也可以不存在, 如果有数据, 就显示, 如果没数据, 就不显示 多个条件的并集
* MUST_NOT: 表示的查询的结果中必须不包含此条件的内容 求的差集
* @throws IOException
*/
@Test
public void booleanQueryToLucene() throws IOException {
BooleanQuery booleanQuery = new BooleanQuery();
NumericRangeQuery<Long> numericRangeQuery = NumericRangeQuery.newLongRange("id",0L,3L,true,true);
booleanQuery.add(numericRangeQuery, BooleanClause.Occur.MUST);//第一个条件, 建议使用必须的条件
TermQuery query = new TermQuery(new Term("content","程序员"));
booleanQuery.add(query, BooleanClause.Occur.MUST_NOT);
baseQuery(booleanQuery);
}
3. lucene的高级
3.1 lucene的高亮
高亮: 本质上就是给关键词的两边添加了高亮的标签,类似于css样式,或者html
public class HighlighterToLucene {
public static void main(String[] args) throws Exception {
//1. 创建查询对象
IndexSearcher indexSearcher = new IndexSearcher(DirectoryReader.open(FSDirectory.open(new File("f:\\index"))));
//2. 执行查询
TermQuery termQuery = new TermQuery(new Term("content","程序员"));
//高亮设置 --------------start-----------------
SimpleHTMLFormatter formatter = new SimpleHTMLFormatter("<font color='red'>","</font>");
QueryScorer scorer = new QueryScorer(termQuery);// 执行用户输入的关键字
Highlighter highlighter = new Highlighter(formatter,scorer);
//高亮设置 --------------end-----------------
TopDocs topDocs = indexSearcher.search(termQuery, Integer.MAX_VALUE);
//2.1 获取结果集中内容
int totalHits = topDocs.totalHits;//共计查询到多少条数据
ScoreDoc[] scoreDocs = topDocs.scoreDocs;//得分文档的数组
//2.2 遍历数组, 获取文档的id值
for (ScoreDoc scoreDoc : scoreDocs) {
float score = scoreDoc.score;//文档的得分
int docID = scoreDoc.doc;//文档的id值
Document document = indexSearcher.doc(docID);
String id = document.get("id");
String title = document.get("title");
String content = document.get("content");
//高亮的获取--------------start--------------
content = highlighter.getBestFragment(new IKAnalyzer(), "content", content);
title = highlighter.getBestFragment(new IKAnalyzer(), "title", title);
//id = highlighter.getBestFragment(new IKAnalyzer(), "id", id);
//高亮的获取--------------end--------------
System.out.println("得分:"+score+" "+id+" "+title+" "+content);
}
}
}
3.2 lucene的排序
public class SortToLucene {
public static void main(String[] args) throws IOException {
//1. 创建查询对象
IndexSearcher indexSearcher = new IndexSearcher(DirectoryReader.open(FSDirectory.open(new File("f:\\index"))));
//2. 执行查询
FuzzyQuery fuzzyQuery = new FuzzyQuery(new Term("content","程序员"));
//sort默认的排序方式是从小到大
//如果想要设置从大到小: 设置true即可. 默认是false
Sort sort = new Sort(new SortField("id", SortField.Type.LONG,true));
TopFieldDocs topFieldDocs = indexSearcher.search(fuzzyQuery, Integer.MAX_VALUE, sort);
int totalHits = topFieldDocs.totalHits;//查询到多少条数据
ScoreDoc[] scoreDocs = topFieldDocs.scoreDocs;//得分文档
for (ScoreDoc scoreDoc : scoreDocs) {
float score = scoreDoc.score;//当前文档的得分
int docId = scoreDoc.doc;
Document doc = indexSearcher.doc(docId);
String id = doc.get("id");
String title = doc.get("title");
System.out.println(id+" "+ title);
}
}
}
3.3 lucene的分页
lucene中压根就不支持的分页, 但是可以使用代码进行模拟出一个的样式来
public class LimitToLucene {
public static void main(String[] args) throws IOException {
//分页的参数
int page = 3; //当前的页数
int pageNumber = 2;//每页显示的条数
//1. 创建查询对象
IndexSearcher indexSearcher = new IndexSearcher(DirectoryReader.open(FSDirectory.open(new File("f:\\index"))));
//2. 执行查询
TermQuery query = new TermQuery(new Term("content","程序员"));
TopDocs topDocs = indexSearcher.search(query, page*pageNumber);
//2.1 获取结果集中内容
int totalHits = topDocs.totalHits;//共计查询到多少条数据
ScoreDoc[] scoreDocs = topDocs.scoreDocs;//得分文档的数组
//2.2 遍历数组, 获取文档的id值
for (int i = (page-1)*pageNumber ; i<totalHits ; i++) {
ScoreDoc scoreDoc = scoreDocs[i];
float score = scoreDoc.score;//文档的得分
int docID = scoreDoc.doc;//文档的id值
Document document = indexSearcher.doc(docID);
String id = document.get("id");
String title = document.get("title");
String content = document.get("content");
System.out.println("得分:"+score+" "+id+" "+title+" "+content);
}
}
}
3.4 lucene激励因子
激励因子: 用来影响文档的打分, 如果在写入索引的时候, 指定了激励因子, 如果激励设置的越大, 那么其对应的文档的在进行查询的时候, 得分越高
public class FolatToLucene {
@Test
public void indexWriterToLucene() throws IOException {
//1. 创建索引写入器对象
Directory directory = FSDirectory.open(new File("f:\\index"));
IndexWriterConfig indexWriterConfig = new IndexWriterConfig(Version.LATEST,new IKAnalyzer());
IndexWriter indexWriter = new IndexWriter(directory,indexWriterConfig);
//2. 添加文档
Document doc = new Document();
LongField longField = new LongField("id",20L, Field.Store.YES);
doc.add(longField);
TextField textField = new TextField("content","程序员是全宇宙最累的人",Field.Store.YES);
//默认, 激励因子是 1
textField.setBoost(10);
doc.add(textField);
indexWriter.addDocument(doc);
//3. 提交文档
indexWriter.commit();
//4. 关闭写入器
indexWriter.close();
}
@Test
public void queryToLucene() throws IOException {
//1. 创建查询对象
IndexSearcher indexSearcher = new IndexSearcher(DirectoryReader.open(FSDirectory.open(new File("f:\\index"))));
//2. 执行查询
TermQuery termQuery = new TermQuery(new Term("content","程序员"));
TopDocs topDocs = indexSearcher.search(termQuery, Integer.MAX_VALUE);
//2.1 获取结果集中内容
int totalHits = topDocs.totalHits;//共计查询到多少条数据
ScoreDoc[] scoreDocs = topDocs.scoreDocs;//得分文档的数组
//2.2 遍历数组, 获取文档的id值
for (ScoreDoc scoreDoc : scoreDocs) {
float score = scoreDoc.score;//文档的得分
int docID = scoreDoc.doc;//文档的id值
Document document = indexSearcher.doc(docID);
String id = document.get("id");
String title = document.get("title");
String content = document.get("content");
System.out.println("得分:"+score+" "+id+" "+title+" "+content);
}
}
}