- 搜索引擎
1.1 什么是搜索引擎
搜索引擎是指根据一定的策略、运用特定的计算机程序从互联网上搜集信息,在对信息进行组织和处理后,为用户提供检索服务,将用户检索相关的信息展示给用户的系统。例如: 百度 谷歌
1.2 `搜索引擎基本的运行原理
1.3 原始数据库查询的缺陷
•慢, 当数据库中的数据量很庞大的时候, 整个的查询效率非常低, 无法及时返回内容
•搜索效果比较差, 只能根据用户输入的完整关键字的进行首尾的模糊匹配
•如果用户输入的关键字出现错别字, 或者多输入了内容, 可能就导致结果远离用户期望的内容
1.4 倒排索引技术
倒排索引, 又称为反向索引: 以字或者词,甚至是一句话一段话作为一个关键字进行索引, 每一个关键字都会对应着一个记录项, 记录项中记录了这个关键字出现在哪些文档中,以及在此文档的什么位置上
为什么说倒排索引可以提升查询的效率和精准度呢?
倒排索引, 是将数据提前按照格式分词放好,建立索引, 当用户进行搜索, 将用户的关键字进行分词, 然后根据分词后的单词到索引库中寻找对应词条,根据词条, 查到对应所在的文档位置, 将其文档内容直接获取即可
2.Lucene
Lucene是Apache提供的一个开源的全文检索引擎工具包, 其本质就是一个工具包, 而非一个完整的搜索引擎, 但是我们可以通过Lucene来构建一个搜索引擎
官方网址: http://lucene.apache.org/
2.1 : Lucene 与solr的关系
•Lucene: 底层的api, 工具包
•solr: 基于Lucene开发的企业级的搜索引擎产品
2.2 使用Lucene如何构建索引
2.2.1 第一步: 导入相关的jar包(pom依赖)
org.apache.lucene
lucene-core
4.10.2
org.apache.lucene
lucene-queries
4.10.2
org.apache.lucene
lucene-test-framework
4.10.2
org.apache.lucene
lucene-analyzers-common
4.10.2
org.apache.lucene
lucene-queryparser
4.10.2
org.apache.lucene
lucene-highlighter
4.10.2
2.2.2 第二步: 书写写入索引的代码
img
//索引写入的入门程序
public class IndexWriterTest {
public static void main(String[] args) throws Exception {
//1. 创建一个索引写入器对象
FSDirectory directory = FSDirectory.open(new File("H:\\test"));
IndexWriterConfig config = new IndexWriterConfig(Version.LATEST,new StandardAnalyzer());
IndexWriter indexWriter = new IndexWriter(directory,config);
//2. 添加原始文档: 文档 看做是一个文件或者数据库中的一个表
Document doc = new Document();
doc.add(new LongField("id",1L, Field.Store.YES));
doc.add(new StringField("title","伟哥, 技术很强大", Field.Store.YES));
doc.add(new TextField("content","伟哥,陪伴以及很久了, 我们已经离不开了, 离开了就不会写代码了", Field.Store.YES));
indexWriter.addDocument(doc);
//3. 提交数据
indexWriter.commit();
//4. 关闭索引写入器
indexWriter.close();
}
}
2.3 索引查看工具
•今日资料中打开索引查看工具, 执行run.bat
2.4 API详解
•IndexWriter: 索引写入器对象
其主要的作用, 添加索引, 修改索引和删除索引
•创建此对象的时候, 需要传入Directory和indexWriterConfig对象
•Directory: 目录类, 用来指定索引库的目录
•常用的实现类:
–FSDirectory: 用来指定文件系统的目录, 将索引信息保存到磁盘上
–优点: 索引可以进行长期保存, 安全系数高
–缺点: 读取略慢
–RAMDriectory: 内存目录, 将索引库信息存放到内存中
–优点: 读取速度快
–缺点: 不安全, 无法长期保存, 关机后就消失了
•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 但是其分词的效果, 是将中文进行一个一个字的分开
针对中文分词一般只能使用第三方的分词词:
一般采用IK分词器
2.5 集成IK分词器
说明: ik分词器官方版本并不支持Lucene4.x版本, 有人基本官方版本做了改进, 使其支持Lucene4.x
•基本使用:
•导入相关依赖, 将分词器切换成ikanalyzer即可
com.janeluo
ikanalyzer
2012_u6
•高级使用:
ik分词器在2012年更新后, 就在没有更新, 其原因就取决于其强大的扩展功能,以保证ik能够持续使用
•ik支持对自定义词库, 其可以定义两个扩展的词典
–扩展词典(新创建词功能):有些词IK分词器不识别 例如:“传智播客”,“碉堡了”
–停用词典(停用某些词功能)有些词不需要建立索引 例如:“哦”,“啊”,“的”
•如何使用:
将此三个文件复制到项目中
接着在ext.dic中设置需要进行分词的内容即可, 在stopword中设置不被分词的内容即可
在使用ik分词器的时候, 一定保证document字段中有一个是支持分词的字段(TextFeild)
2.6 查询索引
2.6.1 查询入门:
//索引查询的入门
@Test
public void indexSearchTest01() throws Exception {
//1. 创建一个索引查询器对象
DirectoryReader reader = DirectoryReader.open(FSDirectory.open(new File(“H:\test”)));
IndexSearcher indexSearcher = new IndexSearcher(reader);
//2. 执行查询:
QueryParser queryParser = new QueryParser("content",new IKAnalyzer());
// 用户输入的关键字
Query query = queryParser.parse("离不开代码了");
//TopDocs : 包含两部分内容: 查询的总条数 doc的得分文档集合(id)
TopDocs topDocs = indexSearcher.search(query, Integer.MAX_VALUE);
int totalHits = topDocs.totalHits; //总条数
ScoreDoc[] scoreDocs = topDocs.scoreDocs; //得分文档的数组(id)
//ScoreDoc :得分文档
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: "+id + "文档的标题"+title + "文档的内容"+ content);
}
}
2.7 查询相关API详解
•IndexSearcher: Lucene中索引查询对象, 用来执行查询和排序操作
•常用方法:
–search(Query query, int n);//执行查询
–参数1: 查询条件
–参数2: 返回的最大条数
–search(Query query, int n,Sort sort);
–参数1: 查询的条件
–参数2: 返回的最大的条数
–参数3: 排序
–doc(int id);//根据文档id查询文档对象
•IndexReader: 索引库读取工具
•使用DirectoryReader来打开索引库
•Query:查询对象
•获取方式:
–通过查询解析器
–单字段的解析器: queryParse
–多字段的解析器: multiFieldQueryParse
–使用Lucene自定义的实现类
–Lucene中提供了五种常用的多样化的查询
•TopDocs:查询结果对象
•第一部分: 查询到的总条数
–int topDocs.totalHits
•第二部分: 得分文档的数组
–ScoreDoc[] topDocs.scoreDocs;
•ScoreDoc: 得分文档对象
•第一部分: 文档的id
–topDoc.doc
•第二部分: 文档的得分
–topDoc.score
2.8 多样化查询(特殊查询)
提取一个查询的方法, 传递不同的query, 即可
//提取一个查询的方法
public void query(Query query) throws Exception{
//1. 创建 查询的核心对象
IndexReader reader = DirectoryReader.open(FSDirectory.open(new File("H:\\test")));
IndexSearcher indexSearcher = new IndexSearcher(reader);
//3. 执行查询
TopDocs topDocs = indexSearcher.search(query, 10);
ScoreDoc[] scoreDocs = topDocs.scoreDocs;//获取得分文档的集合
for (ScoreDoc scoreDoc : scoreDocs) {
//获取文档id
int docId = scoreDoc.doc;
//获取文档得分
float score = scoreDoc.score;
//根据id获取文档
Document doc = indexSearcher.doc(docId);
String content = doc.get("content");
String title = doc.get("title");
System.out.println("文档得分为"+score+content+" "+title);
}
}
2.8.1词条查询: TermQuery
//1.多样化查询: 词条查询
// 词条查询是一个不可在分割内容, 可以把词条看做是一个分词后的单词
//词条查询在书写词条内容的时候, 是不允许输入错误的
// 可以使用词条查询: 查询 不需要在进行分词的字段: id(StringFeild)
@Test
public void termQeryTest() throws Exception {
//1. 创建termQuery对象
TermQuery query = new TermQuery(new Term(“content”,“离不开”));
publicSearch(query);
}
2.8.2通配符查询: WildcardQuery
//2. 通配符查询: WildcardQuery : 类似SQL中的like查询: _ 和 %
// * : 占用 0 ~ 多个字符
// ? : 占用一个字符
@Test
public void wildcardQueryTest() throws Exception {
WildcardQuery query = new WildcardQuery(new Term(“content”,“离不开*”));
publicSearch(query);
}
2.8.3模糊查询: FuzzyQuery
//3. 模糊查询: FuzzyQuery
// 最大编辑次数: 2(0~2)
// 2 : 替换, 修改, 补位 : 这三种操作只要能够在2次(每一此都是单个字符和单个字符的处理)的范围内将词条恢复回来, 就可以查询到数据
// 过半机制: 如果词条是小于等于4的, 最大编辑次数就为1了, 如果只有二个, 压根就没有最大编辑次数
@Test
public void fuzzyQueryTest() throws Exception {
FuzzyQuery query = new FuzzyQuery(new Term("content","lucene"),2);
publicSearch(query);
}
2.8.4数值范围查询: NumericRangeQuery
//4. 数值范围查询:NumericRangeQuery
//
@Test
public void numericRangeQueryTest() throws Exception {
//参数1: 默认查询的字段
//参数2: 最小值
//参数3: 最大值
//参数4: 是否包含最小值
//参数五: 是否包含最大值
NumericRangeQuery query =NumericRangeQuery.newLongRange(“id”,1L,10L,true,false) ;
publicSearch(query);
}
2.8.5组合查询: BooleanQuery
//5. 组合查询: BooleanQuery
// 组合查询:本身自己是没有任何的条件的, 组合查询的目的是为了将其他的查询条件, 并入组合查询的条件中, 实现多条件查询
// MUST : 必须的. 这个条件是必须存在的, 获取到的结果, 必须是包含这个条件的内容
// MUST_NOT: 不必须. 这个条件是必须不包含的, 获取到的结果, 是不能有这个条件里的内容
// SHOULD: 可选的, 如果这个条件能获取到数据, 那么就展示, 如果没有获取到数据, 就不展示, 同时也不会影响其他的条件
@Test
public void booleanQueryTest() throws Exception {
BooleanQuery query = new BooleanQuery();
NumericRangeQuery numericRangeQuery = NumericRangeQuery.newLongRange(“id”,5L,10L,true,false) ;
TermQuery termQuery = new TermQuery(new Term(“content”,“一带一路”));
query.add(numericRangeQuery, BooleanClause.Occur.MUST); //这个条件是一个必须存在
query.add(termQuery,BooleanClause.Occur.SHOULD);
publicSearch(query);
}
2.8 Lucene的索引修改
//索引修改: 先删除, 后添加
@Test
public void updateIndex() throws Exception {
//1. 创建索引写入器的对象
FSDirectory directory = FSDirectory.open(new File(“H:\test”));
IndexWriterConfig config = new IndexWriterConfig(Version.LATEST,new IKAnalyzer());
IndexWriter indexWriter = new IndexWriter(directory,config);
//2. 修改索引的操作
Document doc = new Document();
doc.add(new StringField("id","30", Field.Store.YES));
indexWriter.updateDocument(new Term("content","一带一路"),doc);
//3. 提交数据
indexWriter.commit();
//4. 关闭索引写入器
indexWriter.close();
}
2.9 Lucene 的索引删除
//索引删除
@Test
public void delIndex() throws Exception {
//1. 创建索引写入器的对象
FSDirectory directory = FSDirectory.open(new File(“H:\test”));
IndexWriterConfig config = new IndexWriterConfig(Version.LATEST, new IKAnalyzer());
IndexWriter indexWriter = new IndexWriter(directory, config);
//2. 添加删除
//indexWriter.deleteAll(); //删除所有的数据
indexWriter.deleteDocuments(new Term("id","30"));
//3. 执行删除
indexWriter.commit();
//4. 关闭索引写入器
indexWriter.close();
}
-
Lucene的高级内容(了解)
3.1 Lucene的高亮显示
•高亮: 实际上高亮其实就给对应的字段添加一个HTML标签,并设置其css样式即可
3.1.1 Lucene的高亮实现
//演示lucene的高亮
public class HighlighterTest {public static void main(String[] args) throws Exception{
//1. 创建一个索引查询器对象 DirectoryReader reader = DirectoryReader.open(FSDirectory.open(new File("H:\\test"))); IndexSearcher indexSearcher = new IndexSearcher(reader); //2. 执行查询: //MultiFieldQueryParser multiFieldQueryParser = new MultiFieldQueryParser(); QueryParser queryParser = new QueryParser("content",new IKAnalyzer()); // 用户输入的关键字 Query query = queryParser.parse("离不开代码陪伴技术"); //TopDocs : 包含两部分内容: 查询的总条数 doc的得分文档集合(id) // 高亮设置: start //1. 创建高亮对象 SimpleHTMLFormatter formatter = new SimpleHTMLFormatter("<font color = 'red'>","</font>"); QueryScorer scorer = new QueryScorer(query); Highlighter highlighter = new Highlighter(formatter,scorer); //高亮的设置:end TopDocs topDocs = indexSearcher.search(query, Integer.MAX_VALUE); int totalHits = topDocs.totalHits; //总条数 ScoreDoc[] scoreDocs = topDocs.scoreDocs; //得分文档的数组(id) //ScoreDoc :得分文档 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); // 高亮获取: end System.out.println("得分:"+score +" 文档的id: "+id + "文档的标题"+title + "文档的内容"+ content); }
}
}
3.2 Lucene的排序
// 排序
@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")); }
}
3.3 Lucene的分页
// 分页
@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; 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")); }
}
3.4 Lucene的加权因子
•Lucene会对搜索的结果的匹配度进行一个加分, 用来表示数据和词条关联性的强弱, 得分越高, 表示匹配度越高, 排名越靠前
•Lucene支持对某一个字段设置加权因子, 来提高其打分, 使其排名更加靠前, 这样当用户搜索的时候, 便可以将此词条对应的文档展示在最前面
TextField textField = new TextField(“content”,
“学习lucene需要掌握搜索引擎的基本原理和lucene创建索引和查询索引,boots”, Store.YES);
textField.setBoost(10);