搜索引擎是指根据一定的策略、运用特定的计算机程序从互联网上搜集信息,在对信息进行组织和处理后,为用户提供检索服务,将用户检索相关的信息展示给用户的系统。搜索引擎包括全文索引、目录索引、元搜索引擎、垂直搜索引擎、集合式搜索引擎、门户搜索引擎与免费链接列表等。
代表性搜索引擎: 谷歌(Google) 百度(baidu)
倒排索引 . 称为反向索引, 可以将一句话, 一个词, 一段话把他进行拆分(分词). 将分好词的数据建立索引, 将其保存的索引库中, 这样当用户来访问的索引库的时候, 将用户输入的内容也进行分词, 将分好词的内容到索引库中查询, 将查询到的内容返回用户
为什么说倒排索引可以提高查询的效率?
假设有一个用户: 输入"谷歌跳槽离开", 先将用户这个输入的内容进行一个分词, (谷歌, 跳槽, 离开),然后将这个三个词语到索引库中进行分开查询, 然后将三个的词语查询的id值返回, 1,2,3,4,5,根据id查询数据库
lucene 是Apache提供的全文检索的工具包, 其本质就是一个工具包, 只不过可以使用lucene帮助我们构建一个全文搜索的引擎
官方网址: http://lucene.apache.org/
lucene: 是一个底层的API, 本质是一个工具包
solr: 是一个企业级的搜索引擎, 其底层就是lucene
<dependency>
<groupId>org.apache.lucenegroupId>
<artifactId>lucene-coreartifactId>
<version>4.10.2version>
dependency>
<dependency>
<groupId>org.apache.lucenegroupId>
<artifactId>lucene-queriesartifactId>
<version>4.10.2version>
dependency>
<dependency>
<groupId>org.apache.lucenegroupId>
<artifactId>lucene-test-frameworkartifactId>
<version>4.10.2version>
dependency>
<dependency>
<groupId>org.apache.lucenegroupId>
<artifactId>lucene-analyzers-commonartifactId>
<version>4.10.2version>
dependency>
<dependency>
<groupId>org.apache.lucenegroupId>
<artifactId>lucene-queryparserartifactId>
<version>4.10.2version>
dependency>
<dependency>
<groupId>org.apache.lucenegroupId>
<artifactId>lucene-highlighterartifactId>
<version>4.10.2version>
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();
}
IndexWriter: 索引写入器对象
其主要的作用, 添加索引, 修改索引和删除索引
Directory: 目录类, 用来指定索引库的目录
IndexWriterConfig: 索引写入器的配置类
//参数值: APPEND CREATE CREATE_OR_APPEND
/**
* APPEND: 表示追加, 如果索引库存在, 就会向索引库中追加数据, 如果索引库不存在, 直接报错
*
* CREATE: 表示创建, 不管索引库有没有, 每一次都是重新创建一个新的索引库
*
* CREATE_OR_APPEND: 如果索引库有, 就会追加, 如果没有 就会创建索引库
默认值也是 CREATE_OR_APPEND
*/
config.setOpenMode(IndexWriterConfig.OpenMode.CREATE_OR_APPEND);
在Lucene中, 每一条数据以文档的形式进行存储, 文档中也有其对应的属性和值, Lucene中一个文档类似数据库的一个表, 表中的字段类似于文档中的字段,只不过这个文档只能保存一条数据
Document看做是一个文件, 文件的属性就是文档的属性, 文件对应属性的值就是文档的属性的值 content
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分词器官方版本并不支持Lucene4.x版本, 有人基本官方版本做了改进, 使其支持Lucene4.x
导包:
<dependency>
<groupId>com.janeluogroupId>
<artifactId>ikanalyzerartifactId>
<version>2012_u6version>
dependency>
//单字段的查询解析器
@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);
}
}
/**
* 词条: 是一个不可在分割的内容, 如果写错了, 或者跟索引库中的词条不匹配, 那么都是查询不出来的
* 词条可以是一个字, 是一个词, 甚至可以是一句话或者一段话
* 主要适用于那些不能够进行分词的字段, 比如: 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);
}
}
//通配符查询
//?: 表示的是一个占位符
//*: 表示的占位0到多个
@Test
public void wildCardQueryToLucene() throws IOException {
WildcardQuery query = new WildcardQuery(new Term("content","蓝瘦香菇*"));
baseQuery(query);
}
/**
* 模糊查询: 最大的编辑次数 2
* 编辑: 替换, 删除, 修改 只要在2次之内能够还原成词条中的内容就可以被查询到
* 编辑的次数: 0~2之间, 默认就是2
*
* @throws IOException
*/
@Test
public void fuzzyQueryToLucene() throws IOException {
FuzzyQuery query = new FuzzyQuery(new Term("content","lucen"),2);
baseQuery(query);
}
//数字范围查询
/**
* 参数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);
}
//组合查询: 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);
}
高亮: 本质上就是给关键词的两边添加了高亮的标签,类似于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("","");
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);
}
}
}
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);
}
}
}
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);
}
}
}
激励因子: 用来影响文档的打分, 如果在写入索引的时候, 指定了激励因子, 如果激励设置的越大, 那么其对应的文档的在进行查询的时候, 得分越高
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);
}
}
}