lucene

  1. 搜索引擎
    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();
}
  1. 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);

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值