• Lucene补充


    1. 课程计划

    1. LuceneField  
    2. Lucene的索引库维护
    3. lucene的查询   

        a) Query子对象  

        b) QueryParser 

      4.Lucene相关度排序(了解

    2. Field

    2.1. Field属性

    Field是文档中的域,包括Field名和Field值两部分,一个文档可以包括多个FieldDocument只是Field的一个承载体,Field值即为要索引的内容,也是要搜索的内容。

    1.是否分词(tokenized)

    是:作分词处理,即将Field值进行分词,分词的目的是为了索引

    比如:商品名称、商品描述等,这些内容用户要输入关键字搜索,由于搜索的内容格式大、内容多需要分词后将语汇单元建立索引

    否:不作分词处理

    比如:商品id、订单号、身份证号等   

    2. 是否索引(indexed)

    是:进行索引。将Field分词后的词或整个Field值进行索引,存储到索引域,索引的目的是为了搜索

    比如:商品名称、商品描述分析后进行索引,订单号、身份证号不用分词但也要索引,这些将来都要作为查询条件。

    否:不索引。

    比如:图片路径、文件路径等,不用作为查询条件的不用索引。

    3.是否存储(stored)

    是:将Field值存储在文档域中,存储在文档域中的Field才可以从Document中获取。

    比如:商品名称、订单号,凡是将来要从Document中获取的Field都要存储。

    否:不存储Field

    比如:商品描述,内容较大不用存储。如果要向用户展示商品描述可以从系统的关系数据库中获取。

    2.2. Field常用类型

    下边列出了开发中常用 Filed类型,注意Field的属性,根据需求选择:

    Field

    数据类型

    Analyzed

    是否分词

    Indexed

    是否索引

    Stored

    是否存储

    说明

    StringField(FieldName, FieldValue,Store.YES))

     

    字符串

    N

    Y

    Y或N

    这个Field用来构建一个字符串Field,但是不会进行分词,会将整个串存储在索引中,比如(订单号,身份证号等)

    是否存储在文档中用Store.YESStore.NO决定

    LongField(FieldName, FieldValue,Store.YES)

    Long

    Y

    Y

    Y或N

    这个Field用来构建一个Long数字型Field,进行分词和索引,比如(价格)FloatField

    是否存储在文档中用Store.YESStore.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的策略.

    2.3. Field修改

    2.3.1. 修改分析

    图书idStringField

    是否分词:不用分词,因为不会根据商品id来搜索商品 

    是否索引:不索引,因为不需要根据图书ID进行搜索

    是否存储:要存储,因为查询结果页面需要使用id这个值。

    图书名称:TextField

    是否分词:要分词,因为要根据图书名称的关键词搜索。

    是否索引:要索引。

    是否存储:要存储。

    图书价格:

    是否分词:要分词,lucene对数字型的值只要有搜索需求的都要分词和索引,因    lucene对数字型的内容要特殊分词处理,需要分词和索引。

    是否索引:要索引

    是否存储:要存储

    图书图片地址:

    是否分词:不分词

    是否索引:不索引

    是否存储:要存储

    图书描述:

    是否分词:要分词

    是否索引:要索引

    是否存储:因为图书描述内容量大,不在查询结果页面直接显示,不存储。

    不存储是不在lucene的索引域中记录,节省lucene的索引文件空间。

    如果要在详情页面显示描述,解决方案:

    lucene中取出图书的id,根据图书的id查询关系数据库(MySQL)中book表得到描述信息。

    2.3.2. 代码修改

    对之前编写的testCreateIndex()方法进行修改。

    代码片段

     1 // Document文档中添加域
     2 
     3 // 图书Id
     4 
     5 // Store.YES:表示存储到文档域中
     6 
     7 // 不分词,不索引,储存
     8 
     9 document.add(new StoredField("id", book.getId().toString()));
    10 
    11 // 图书名称
    12 
    13 // 分词,索引,储存
    14 
    15 document.add(new TextField("name", book.getName().toString(), Store.YES));
    16 
    17 // 图书价格
    18 
    19 // 分词,索引,储存
    20 
    21 document.add(new FloatField("price", book.getPrice(), Store.YES));
    22 
    23 // 图书图片地址
    24 
    25 // 不分词,不索引,储存
    26 
    27 document.add(new StoredField("pic", book.getPic().toString()));
    28 
    29 // 图书描述
    30 
    31 // 分词,索引,不储存
    32 
    33 document.add(new TextField("desc", book.getDesc().toString(), Store.NO));

    3. 索引维护

    3.1. 需求

    管理人员通过电商系统更改图书信息,这时更新的是关系数据库,如果使用lucene搜索图书信息,需要在数据库表book信息变化时及时更新lucene索引库。

    3.2. 添加索引

    调用 indexWriter.addDocumentdoc)添加索引。

    参考入门程序的创建索引。

    3.3. 删除索引

    3.3.1. 删除指定索引

    根据Term删除索引,满足条件的将全部删除。

     1 /**
     2 *@author 作者: WangXS
     3 *@version 日期: 2018年10月9日 下午4:29:05
     4 *
     5 * 索引维护   --删除索引
     6 *删除指定索引
     7 *删除全部索引
     8 */
     9 public class DeleteIndexTest {
    10     //获取IndexWriter写入对象
    11     public IndexWriter getIndexWriter() throws IOException {
    12         //3.创建分析器(分词器)支持中文
    13         IKAnalyzer analyzer = new IKAnalyzer();
    14         //4.创建IndexWriterConfig配置信息类
    15         IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_4_10_3, analyzer);
    16         //5.创建Directory对象,声明索引库存储位置
    17         Directory directory = FSDirectory.open(new File("F:\temp\index"));
    18         //6.创建IndexWriter写入对象
    19         IndexWriter indexWriter  = new IndexWriter(directory, config);
    20         return indexWriter;
    21     }
    22     
    23     @Test
    24     public void testDeleteIndex() throws Exception {
    25         // 获取写入对象
    26         IndexWriter indexWriter = getIndexWriter();
    27         //小心被祭天~
    28         //删除全部索引
    29         //indexWriter.deleteAll();
    30         //删除指定索引
    31         Query query = new TermQuery(new Term("name", "apache"));
    32         //indexWriter.deleteDocuments(new Term("name", "apache"));
    33         //释放资源
    34         indexWriter.close();
    35     }
    36 }

    3.3.2. 删除全部索引(慎用)

    将索引目录的索引信息全部删除,直接彻底删除,无法恢复

    建议参照关系数据库基于主键删除方式,所以在创建索引时需要创建一个主键Field,删除时根据此主键Field删除。

    索引删除后将放在Lucene的回收站中,Lucene3.X版本可以恢复删除的文档,3.X之后无法恢复。

    3.4. 修改索引

    更新索引是先删除再添加,建议对更新需求采用此方法并且要保证对已存在的索引执行更新,可以先查询出来,确定更新记录存在执行更新操作。

    如果更新索引的目标文档对象不存在则执行添加

    代码

     1 /**
     2 *@author 作者: WangXS
     3 *@version 日期: 2018年10月9日 下午4:29:05
     4 *索引维护   --更新索引
     5 */
     6 public class UpdateIndexTest {
     7     @Test
     8     public void testUpdateIndex() throws Exception {
     9     //创建文档对象
    10     Document document = new Document();
    11     // Document文档中添加Field域
    12     // 图书Id
    13     // Store.YES:表示存储到文档域中  
    14     // 不分词,不索引,储存
    15     document.add(new StoredField("ID", "007"));
    16     // 图书名称
    17     // 分词,索引,储存
    18     document.add(new TextField("NAME", "大内密探", Store.YES));
    19     // 图书描述
    20     // 分词,索引,不储存
    21     document.add(new TextField("desc", "零零八.........", Store.NO));
    22     //3.创建分析器(分词器)
    23     //StandardAnalyzer standardAnalyzer = new StandardAnalyzer();
    24      IKAnalyzer analyzer = new IKAnalyzer();
    25     //4.创建IndexWriterConfig配置信息类
    26     IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_4_10_3, analyzer);
    27     //5.创建Directory对象,声明索引库存储位置
    28     Directory directory = FSDirectory.open(new File("F:\temp\index"));
    29     //6.创建IndexWriter写入对象
    30     IndexWriter indexWriter  = new IndexWriter(directory, config);
    31     //7.把Document写入到索引库中
    32     indexWriter.updateDocument(new Term("name","apache"), document);
    33     //8.释放资源
    34     indexWriter.close();
    35     
    36     }
    37 }

    4. 搜索

    4.1. 创建查询的两种方法

    对要搜索的信息创建Query查询对象,Lucene会根据Query查询对象生成最终的查询语法。类似关系数据库Sql语法一样,Lucene也有自己的查询语法,比如:“name:lucene”表示查询名字为nameField域中的“lucene”的文档信息。

    可通过两种方法创建查询对象:

    1)使用Lucene提供Query子类

    Query是一个抽象类,lucene提供了很多查询对象,比如TermQuery项精确查询,NumericRangeQuery数字范围查询等。

    如下代码:

    Query query = new TermQuery(new Term("name", "lucene"));

    2)使用QueryParse解析查询表达式

    QueryParser会将用户输入的查询表达式解析成Query对象实例。

    如下代码:

    QueryParser queryParser = new QueryParser("name", new IKAnalyzer());

    Query query = queryParser.parse("name:lucene");

    4.2. 通过Query子类搜索

    4.2.1. TermQuery

    TermQuery词项查询,TermQuery不使用分析器,搜索关键词进行精确匹配Field域中的词,比如订单号、分类ID号等。 Where name =思念Spring

    搜索对象创建:

     1 public class QueryIndexTest {
     2     //获取IndexWriter写入对象
     3     public IndexSearcher getIndexSearcher() throws IOException {
     4         //2. 创建Directory流对象,声明索引库位置
     5         Directory directory = FSDirectory.open(new File("F:\temp\index"));
     6         //3. 创建索引读取对象IndexReader
     7         IndexReader indexReader = DirectoryReader.open(directory);
     8         //4. 创建索引搜索对象IndexSearcher
     9         IndexSearcher search = new IndexSearcher(indexReader);
    10         return search;
    11     }
    12     @Test
    13     public void testSearchIndex() throws Exception {
    14         //1. 创建Query搜索对象
    15         // 创建搜索解析器,第一个参数:默认Field域,第二个参数:分词器
    16         IndexSearcher searcher = getIndexSearcher();
    17         Query query = new TermQuery(new Term("name","java"));
    18         //5. 使用索引搜索对象,执行搜索,返回结果集TopDocs
    19         // 第一个参数:搜索对象,第二个参数:返回的数据条数,指定查询结果最顶部的n条数据返回
    20         printResult(searcher, query);    
    21     }
    22     //打印结果
    23     public void printResult(IndexSearcher searcher, Query query) throws IOException {
    24         TopDocs topDocs = searcher.search(query, 5);
    25         System.out.println("查询到的数据总条数是:" + topDocs.totalHits);
    26         //6. 解析结果集
    27         ScoreDoc[] scoreDocs = topDocs.scoreDocs;
    28             for (ScoreDoc scoreDoc : scoreDocs) {
    29                 //获取文档
    30                 int docId = scoreDoc.doc;
    31                 Document doc = searcher.doc(docId);
    32                 System.out.println("=============================");
    33                 System.out.println("docID:" + docId);
    34                 System.out.println("bookId:" + doc.get("id"));
    35                 System.out.println("name:" + doc.get("name"));
    36                 System.out.println("price:" + doc.get("price"));
    37                 System.out.println("pic:" + doc.get("pic"));
    38                 // System.out.println("desc:" + doc.get("desc"));
    39             }
    40         //7. 释放资源
    41         searcher.getIndexReader().close();
    42     }
    43 }

    4.2.2. NumericRangeQuery

    NumericRangeQuery,指定数字范围查询.

     1 @Test
     2 
     3 public void testSearchNumericRangeQuery() throws Exception {
     4 
     5 // 创建NumericRangeQuery搜索对象,数字范围查询.
     6 
     7 // 五个参数分别是:域名、最小值、最大值、是否包含最小值,是否包含最大值
     8 
     9 Query query = NumericRangeQuery.newFloatRange("price", 54f, 56f, false, true);
    10 
    11 doSearch(query);
    12 
    13 }

     

    4.2.3. BooleanQuery

    BooleanQuery,布尔查询,实现组合条件查询。

     1 //BooleanQuery,布尔查询,实现组合条件查询。
     2     @Test
     3     public void testBooleanQuery() throws Exception {
     4         //1. 创建Query搜索对象
     5         // 创建搜索解析器,第一个参数:默认Field域,第二个参数:分词器
     6         IndexSearcher searcher = getIndexSearcher();
     7         Query query1 = NumericRangeQuery.newFloatRange("price", 70f, 80f, true, true);
     8         Query query2 = new TermQuery(new Term("name","java"));
     9         BooleanQuery query = new BooleanQuery();
    10         query.add(query1, BooleanClause.Occur.MUST);
    11         query.add(query2, BooleanClause.Occur.MUST);
    12         //5. 使用索引搜索对象,执行搜索,返回结果集TopDocs
    13         // 第一个参数:搜索对象,第二个参数:返回的数据条数,指定查询结果最顶部的n条数据返回
    14         printResult(searcher, query);    
    15     }

    组合关系代表的意思如下:

         1MUSTMUST表示“与”的关系,即“交集”。

         2MUSTMUST_NOT前者包含后者不包含。

         3MUST_NOTMUST_NOT没意义

         4SHOULDMUST表示MUSTSHOULD失去意义;

         5SHOULDMUST_NOT相当于MUSTMUST_NOT

         6SHOULDSHOULD表示“或”的关系,即“并集”。

    4.3. 通过QueryParser搜索

    通过QueryParser也可以创建QueryQueryParser提供一个Parse方法,此方法可以直接根据查询语法来查询。可以通过打印Query对象的方式,查看生成的查询语句。

    4.3.1. 查询语法

    1、基础的查询语法,关键词查询:

    域名+:+搜索的关键字

    例如:name:java

    2、范围查询

    域名+:+[最小值 TO 最大值]

    例如:size:[1 TO 1000]

    注意:QueryParser不支持对数字范围的搜索,它支持字符串范围。数字范围搜索建议使用NumericRangeQuery

    3、组合条件查询

    Occur.MUST 查询条件必须满足,相当于AND

    +(加号)

    Occur.SHOULD 查询条件可选,相当于OR

    空(不用符号)

    Occur.MUST_NOT 查询条件不能满足,相当于NOT

    -(减号)

    4.3.2. QueryParser

     1 @Test
     2 
     3 public void testSearchIndex() throws Exception {
     4 
     5 // 创建分词器
     6 
     7 Analyzer analyzer = new StandardAnalyzer();
     8 
     9 // 1. 创建Query搜索对象
    10 
    11 // 创建搜索解析器,第一个参数:默认Field域,第二个参数:分词器
    12 
    13 QueryParser queryParser = new QueryParser("desc", analyzer);
    14 
    15 // 创建搜索对象
    16 
    17 // Query query = queryParser.parse("desc:java学习");
    18 
    19 Query query = queryParser.parse("desc:java AND lucene");
    20 
    21 // 打印生成的搜索语句
    22 
    23 System.out.println(query);
    24 
    25 // 执行搜索
    26 
    27 doSearch(query);
    28 
    29 }

    4.3.3. MultiFieldQueryParser

    通过MultiFieldQueryParse对多个域查询。

     1 @Test
     2 
     3 public void testSearchMultiFieldQueryParser() throws Exception {
     4 
     5 // 创建分词器
     6 
     7 Analyzer analyzer = new IKAnalyzer();
     8 
     9 // 1. 创建MultiFieldQueryParser搜索对象
    10 
    11 String[] fields = { "name", "desc" };
    12 
    13 MultiFieldQueryParser multiFieldQueryParser = new MultiFieldQueryParser(fields, analyzer);
    14 
    15 // 创建搜索对象
    16 
    17 Query query = multiFieldQueryParser.parse("lucene");
    18 
    19 // 打印生成的搜索语句
    20 
    21 System.out.println(query);
    22 
    23 // 执行搜索
    24 
    25 doSearch(query);
    26 
    27 }

    生成的查询语句:

    name:lucene desc:lucene

    4.4. TopDocs

    Lucene搜索结果可通过TopDocs遍历,TopDocs类提供了少量的属性,如下:

    方法或属性

    说明

    totalHits

    匹配搜索条件的总记录数

    scoreDocs

    顶部匹配记录

    注意:

    Search方法需要指定匹配记录数量nindexSearcher.search(query, n)

    TopDocs.totalHits:是匹配索引库中所有记录的数量

    TopDocs.scoreDocs:匹配相关度高的前边记录数组,scoreDocs的长度小于等于search方法指定的参数n

    5. 相关度排序(了解)

    5.1. 什么是相关度排序

    相关度排序是查询结果按照与查询关键字的相关性进行排序,越相关的越靠前。比如搜索Lucene”关键字,与该关键字最相关的文章应该排在前边。

    5.2. 相关度打分  

    Lucene对查询关键字和索引文档的相关度进行打分,得分高的就排在前边。如何打分呢?Lucene是在用户进行检索时实时根据搜索的关键字计算出来的,分两步:

    1)计算出词(Term)的权重   

    2)根据词的权重值,计算文档相关度得分。

    什么是词的权重?

    通过索引部分的学习,明确索引的最小单位是一个Term(索引词典中的一个词)。搜索也是从索引域中查询Term,再根据Term找到文档。Term对文档的重要性称为权重,影响Term权重有两个因素:

    l Term Frequency (tf)

    指此Term在此文档中出现了多少次。tf 越大说明越重要。

    (Term)在文档中出现的次数越多,说明此词(Term)对该文档越重要,如“Lucene”这个词,在文档中出现的次数很多,说明该文档主要就是讲Lucene技术的。

    l Document Frequency (df)

    指有多少文档包含此Termdf 越大说明越不重要。

    比如,在一篇英语文档中,this出现的次数更多,就说明越重要吗?不是的,有越多的文档包含此词(Term), 说明此词(Term)太普通,不足以区分这些文档,因而重要性越低。

    5.3. 设置boost值影响相关度排序

    boost是一个加权值(默认加权值为1.0f),它可以影响权重的计算。在索引时对某个文档中的field设置加权值,设置越高,在搜索时匹配到这个文档就可能排在前边。

    未设置权重:

    希望把namespring的排名提高

    先清空索引库,然后修改创建索引的代码,添加设置加权值的逻辑

    修改创建索引代码:

     1 public class CreateIndexTest {
     2     @Test
     3     public void testCreateIndex() throws Exception {
     4     //1.采集数据
     5     BookDao bd = new BookDaoImpl();
     6     List<Book> bookList = bd.queryBookList();
     7     //2.创建Document文档对象
     8     List<Document> documents = new ArrayList<>();
     9     for (Book book : bookList) {
    10         Document document = new Document();
    11         // Document文档中添加Field域
    12         // 图书Id
    13         // Store.YES:表示存储到文档域中  
    14         // 不分词,不索引,储存
    15         document.add(new StoredField("id", book.getId().toString()));
    16         // 图书名称
    17         // 分词,索引,储存
    18         TextField nameField = new TextField("name", book.getName().toString(), Store.YES);
    19         if (book.getId()==4) {
    20             nameField.setBoost(10);        
    21         }
    22         document.add(nameField);
    23         // 图书价格
    24         // 分词,索引,储存
    25         document.add(new FloatField("price", book.getPrice(), Store.YES));
    26         // 图书图片地址
    27         // 不分词,不索引,储存
    28         document.add(new StoredField("pic", book.getPic().toString()));
    29         // 图书描述
    30         // 分词,索引,不储存
    31         document.add(new TextField("desc", book.getDesc().toString(), Store.NO));
    32         // 把Document放到list中
    33         documents.add(document);
    34     }
    35     //3.创建分析器(分词器)
    36     //StandardAnalyzer standardAnalyzer = new StandardAnalyzer();
    37      IKAnalyzer analyzer = new IKAnalyzer();
    38     //4.创建IndexWriterConfig配置信息类
    39     IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_4_10_3, analyzer);
    40     //5.创建Directory对象,声明索引库存储位置
    41     Directory directory = FSDirectory.open(new File("F:\temp\index"));
    42     //6.创建IndexWriter写入对象
    43     IndexWriter indexWriter  = new IndexWriter(directory, config);
    44     //7.把Document写入到索引库中
    45     for (Document document : documents) {
    46         indexWriter.addDocument(document);
    47     }
    48     //8.释放资源
    49     indexWriter.close();
    50  
    51     }
    52 }

     

  • 相关阅读:
    IDEA大于等于,不等于、小于等于等等符号设置
    大众点评订单系统分库分表实践
    oracle 监听 hang住
    Linux_查看网卡使用的网络带宽情况
    Git_暂存功能
    Python_镜像安装源
    java8新特性专题之六、筛选和切片
    java8新特性专题之四、lambda方法引用和构造器引用
    java8新特性专题之二、lambda基础语法
    java8新特性专题之八、java中Optional的使用详细解析
  • 原文地址:https://www.cnblogs.com/itworkerlittlewrite/p/9769192.html
Copyright © 2020-2023  润新知