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 }

 

原文地址:https://www.cnblogs.com/itworkerlittlewrite/p/9769192.html