技术 lucene
@[toc]
―、什么是全文检索
1、数据的分类
1.结构化数据
格式固定、长度固定、数据类型固定
例如数据库中的数据
2.非结构化数据
word文档,pdf文档、邮件、html、xml
格式不固定、长度不固定、数据类型不固定
2、数据的査词
1.结构化数据的査词
SQL语句,査词结构化数据的方法。简单、速度快
2.非结构化数据的査词
从文本文件中找出包含spring单词的文件,
1.目测
2.使用程序把文档读取到内存中,然后匹配字符串。顺序扫描
3.把非结构化数据变成结构化数据
先根据空格进行字符拿拆分,得到一个单词列表.基于单词列表创建一个索引
然后查询索引,根据单词和文档的对应关系找到文档列表,这个过程叫做全文检索
索引: 一个为了提高査词速度.创建某种数据结构的集合
3、全文检索
先创建索引然后查询索引的过程叫做全文检索
索引一次创建可以多次使用,表现为每次查询速度很快.
二、 全文检索的应用场景
1、 搜索引擎
百度、360搜索、谷歌、搜狗
2、 站内搜京
论坛搜索、微博、文章搜索
3、 电商搜索
淘宝搜索、京东搜索
4、 只要是有搜索的地方就可以使用全文检索技术
三、 什么是Lucene
Lucene是一个基于java开发全文检索工具包
四、 Lucene实现全文检索的流程
1.创建索引
1.获得文档
原始文档:要基于那些数据来进行搜索.那么这些数据就是原始文档
搜索引擎:使用爬虫获得原始文档
站内搜索:数据库中的数据。
案例:直接使用 io 流读取磁盘上的文件。
2.构建文档对象
对应每个原始文档创建一个 Document 对象
每个Document对象中包含多个域( field )
域中保存就是原始文档数据。
域的名称
域的值
每个文档都有一个唯一的编号,就是文档id
3.分析文档
就是分词的过程
1.根据空格进行字符串拆分.得到一个单词列表
2.把单词统一转换成小写。
3.去除标点符号
4.去除停用词
停用词:无意义的词
每个关键词都封装成一个下 Term对象中。
Term中包含两部分内容:
关键词所在的域
关键词本者
不同的域中拆分出来的相同的关键词是不同的 Term
4.创建索引
基于关键词列表创建一个索引.保存到索引库中。
索引库中:
索引
document 对象
关键词和文档的对应关系
通过词语找文档.这种索引的结构叫倒排索引结构。
2.查询索引
1.用户查询接口
用户输入查询条件的地方
例如:百度的搜索框
2.把关键词封装成一个查询对象
要查询的域
要搜索的关键词
3.执行查询
根据要查询的关键词到对应的域上进行搜索。
找到关键词,根据关键词找到对应的文档
4.渲染结果
根据文档的 id 找到文档对象
对关键词进行高亮显示
分页处理
最终展示给用户看。
五、入门程序
1 .创建索引
环境:
需要下载 Lucene
http//lucene.apache.org/
最低要求jdk1.8
工程搭建:
创建一个 java 工程
添加 jar :
lucene-analyzers-common-7.4.0.jar
lucene-core-7.4.0.jar
commons-io-jar
步骤:
1. 创建一个Director对象.指定索引库保存的位置 。
2. 基于 Directory 对象创建一个 IndexWriter对象
3. 读取磁盘上的文件.对应每个文件创建一个文档对象。
4. 向文档对象中添加域
5. 把文档对象写入索引库
关闭 indexwriter 对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48//创建索引
public void createIndex() throws Exception {
//指定索引库存放的路径
//D:\temp\index
Directory directory = FSDirectory.open(new File("D:\\temp\\index").toPath());
//索引库还可以存放到内存中
//Directory directory = new RAMDirectory();
//创建indexwriterCofig对象
IndexWriterConfig config = new IndexWriterConfig();
//创建indexwriter对象
IndexWriter indexWriter = new IndexWriter(directory, config);
//原始文档的路径
File dir = new File("D:\\temp\\searchsource");
for (File f : dir.listFiles()) {
//文件名
String fileName = f.getName();
//文件内容
String fileContent = FileUtils.readFileToString(f);
//文件路径
String filePath = f.getPath();
//文件的大小
long fileSize = FileUtils.sizeOf(f);
//创建文件名域
//第一个参数:域的名称
//第二个参数:域的内容
//第三个参数:是否存储
Field fileNameField = new TextField("filename", fileName, Field.Store.YES);
//文件内容域
Field fileContentField = new TextField("content", fileContent, Field.Store.YES);
//文件路径域(不分析、不索引、只存储)
Field filePathField = new TextField("path", filePath, Field.Store.YES);
//文件大小域
Field fileSizeField = new TextField("size", fileSize + "", Field.Store.YES);
//创建document对象
Document document = new Document();
document.add(fileNameField);
document.add(fileContentField);
document.add(filePathField);
document.add(fileSizeField);
//创建索引,并写入索引库
indexWriter.addDocument(document);
}
//关闭indexwriter
indexWriter.close();
}
2. 使用 luke 查看索引库中的内容
3. 查询索引库
步骤:
1. 创建一个 Directory 对象,指定索引库的位置
2. 创建一个 IndexReader 对象
3. 创建一个 IndexSearcher 对象.构造方法中的参数 indexReader 对象。
4. 创建一个 Query对象,TermQuery
5. 执行查询,得到一个 TopDocs 对象
6. 取查询结果的总记录数
7. 取文档列表
8. 打印文档中的内容
关闭 indexReader 对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32//查询索引库
public void searchIndex() throws Exception {
//指定索引库存放的路径
//D:\temp\index
Directory directory = FSDirectory.open(new File("D:\\temp\\index").toPath());
//创建indexReader对象
IndexReader indexReader = DirectoryReader.open(directory);
//创建indexsearcher对象
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
//创建查询
Query query = new TermQuery(new Term("filename", "apache"));
//执行查询
//第一个参数是查询对象,第二个参数是查询结果返回的最大值
TopDocs topDocs = indexSearcher.search(query, 10);
//查询结果的总条数
System.out.println("查询结果的总条数:"+ topDocs.totalHits);
//遍历查询结果
//topDocs.scoreDocs存储了document对象的id
for (ScoreDoc scoreDoc : topDocs.scoreDocs) {
//scoreDoc.doc属性就是document对象的id
//根据document的id找到document对象
Document document = indexSearcher.doc(scoreDoc.doc);
System.out.println(document.get("filename"));
//System.out.println(document.get("content"));
System.out.println(document.get("path"));
System.out.println(document.get("size"));
System.out.println("-------------------------");
}
//关闭indexreader对象
indexReader.close();
}
六、分析器
默认使用的数标准分析器 StandardAnalyzer
1. 查看分析器的分析效果
使用 Analyzer 对象的 tokenStream 方法返回一个 TokenStream 对象。词对象中包含了最终分词结果。
实现步骤:
1. 创建一个Analyzer对象,StandardAnalyzer对象
2. 使用分析器对象的 tokenStream 方法获得一个 TokenStream 对象
3. 向 TokenStream 对象中设置一个引用,相当于数一个指针
4. 调用 TokenStream 对象的 reset 方法。如果不调用抛异常
5. 使用 while 循环遍历 TokenStream 对象
关闭TokenStream对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26//查看标准分析器的分词效果
public void testTokenStream() throws Exception {
//创建一个标准分析器对象
Analyzer analyzer = new StandardAnalyzer();
//获得tokenStream对象
//第一个参数:域名,可以随便给一个
//第二个参数:要分析的文本内容
TokenStream tokenStream = analyzer.tokenStream("test", "The Spring Framework provides a comprehensive programming and configuration model.");
//添加一个引用,可以获得每个关键词
CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class);
//添加一个偏移量的引用,记录了关键词的开始位置以及结束位置
OffsetAttribute offsetAttribute = tokenStream.addAttribute(OffsetAttribute.class);
//将指针调整到列表的头部
tokenStream.reset();
//遍历关键词列表,通过incrementToken方法判断列表是否结束
while(tokenStream.incrementToken()) {
//关键词的起始位置
System.out.println("start->" + offsetAttribute.startOffset());
//取关键词
System.out.println(charTermAttribute);
//结束位置
System.out.println("end->" + offsetAttribute.endOffset());
}
tokenStream.close();
}
2. IKAnalyzer 的便用方法(中文分析器)
1.把 IKAnalyzer 的 jar 包添加到工程中
2.把配置文件和扩展词典添加到工程的 classpath 下
注意:扩展词典严禁使用weindows记事本编辑保证扩展词典的编码格式是utf-8
扩展词典:添加一些新词
停用词词典:无意义的词或者是敏感词汇
1 |
|
七、索引库维护
-
Field域的属性
是否分析:是否对域的内容进行分词处理。前提是我们要对域的内容进行查询。
是否索引:将Field分析后的词或整个Field值进行索引,只有索引方可搜索到。
比如:商品名称、商品简介分析后进行索引,订单号、身份证号不用分析但也要索引,这些将来都要作为查询条件。
是否存储:将Field值存储在文档中,存储在文档中的Field才可以从Document中获取
比如:商品名称、订单号,凡是将来要从Document中获取的Field都要存储。
是否存储的标准:是否要将内容展示给用户
Field类 数据类型 Analyzed 是否分析 Indexed 是否索引 Stored 是否存储 说明 StringField(FieldName, FieldValue,Store.YES)) 字符串 N Y Y或N 这个Field用来构建一个字符串Field,但是不会进行分析,会将整个串存储在索引中,比如(订单号,姓名等) 是否存储在文档中用Store.YES或Store.NO决定 LongPoint(String name, long… point) Long型 Y Y N 可以使用LongPoint、IntPoint等类型存储数值类型的数据。让数值类型可以进行索引。但是不能存储数据,如果想存储数据还需要使用StoredField。 StoredField(FieldName, FieldValue) 重载方法,支持多种类型 N N Y 这个Field用来构建不同类型Field 不分析,不索引,但要Field存储在文档中 TextField(FieldName, FieldValue, Store.NO) 或 TextField(FieldName, reader) 字符串 或 流 Y Y Y或N 如果是一个Reader, lucene猜测内容比较多,会采用Unstored的策略. -
添加文档
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25//添加索引
public void addDocument() throws Exception {
//索引库存放路径
Directory directory = FSDirectory.open(new File("D:\\temp\\index").toPath());
IndexWriterConfig config = new IndexWriterConfig(new IKAnalyzer());
//创建一个indexwriter对象
IndexWriter indexWriter = new IndexWriter(directory, config);
//创建一个Document对象
Document document = new Document();
//向document对象中添加域。
//不同的document可以有不同的域,同一个document可以有相同的域。
document.add(new TextField("filename", "新添加的文档", Field.Store.YES));
document.add(new TextField("content", "新添加的文档的内容", Field.Store.NO));
//LongPoint创建索引
document.add(new LongPoint("size", 1000l));
//StoreField存储数据
document.add(new StoredField("size", 1000l));
//不需要创建索引的就使用StoreField存储
document.add(new StoredField("path", "d:/temp/1.txt"));
//添加文档到索引库
indexWriter.addDocument(document);
//关闭indexwriter
indexWriter.close();
}
-
删除文档
删除全部 (说明:将索引目录的索引信息全部删除,直接彻底删除,无法恢复)
1
2
3
4
5
6
7
8
9//删除全部索引
public void deleteAllIndex() throws Exception {
IndexWriter indexWriter = getIndexWriter();
//删除全部索引
indexWriter.deleteAll();
//关闭indexwriter
indexWriter.close();
}根据查询、关键词删除文档 ,
1
2
3
4
5
6
7
8
9
10
11//根据查询条件删除索引
public void deleteIndexByQuery() throws Exception {
IndexWriter indexWriter = getIndexWriter();
//创建一个查询条件
Query query = new TermQuery(new Term("filename", "apache"));
//根据查询条件删除
indexWriter.deleteDocuments(query);
//关闭indexwriter
indexWriter.close();
}
3. 修改文档
修改的原理是先删除后添加
1 | //修改索引库 |
八、索引库查询
1. 使用 Query 的子类
1. TermQuery
根据关键词进行查询。
需要指定要查询的域及要查询的关键词
1 | //使用Termquery查询 |
2. RangeQuery
范围查询
1 |
|
2. 使用 QueryPaser 解析查询表达式
- 可以对要查询的内容先分词,然后基于分词的结果进行查询。
- 添加一个jar包
lucene-queryparser-7.4.0.jar
1 |
|