Lucene
1 . 介绍 :
Lucene 是 Apache 下的 Jakarta 项目组下的使用 java 语言编写高性能高扩展的全文搜索引擎子项目,是一个开源的 全文检索引擎 的工具包, 但不是完整的一个全文检索引擎, 而是一个全文检索引擎的架构, 提供了完整的查询引擎和索引引擎, 部分的文本分析引擎, Lucene的 目的是为开发人员提供一个简单易用的全文检索的工具包用在需要的地方, 或者在其基础上编写更加完善的全文搜索引擎.
1-1 . 什么是全文检索引擎 :
全文检索引擎即通过从互联网上提取各个网站的信息而建立的数据库, 检索用户查询条件匹配的记录,进行排序返回给用户.
全文检索引擎的执行流程 :
1-2 . 索引组件 :
步骤 :
-
获取原始文档(即互联网或者磁盘上需要搜索的原始信息)
-
建立文档
将内容转换为部件即 : 文档 , 而文档主要包括 标题 , 正文 , 作者 , 链接 等带有值的 域 .
-
文档分析
将域中的内容分割为一系列的语汇单元的原子元素 .
-
文档索引
将文档加入索引列表 .
创建索引是对语汇单元索引,通过词语找文档,这种索引的结构叫倒排索引结构。
1-3 . 搜索组件 :
搜索处理过程就是从索引中查找单词,从而找到包含单词的文档,搜索质量主要由查准率,查全率来衡量.
步骤 :
-
用户搜索界面 :
用户搜索界面是用户与搜索程序交互时,通过浏览器,桌面程序与移动设备中的可视界面 .
-
建立查询 :
当用户使用你的搜索程序时, 通常他们会提交一个搜索请求, 但是通常以 HTML 表单或者 AJAX 请求的形式由浏览器提交到引擎服务器, 然后将这个请求转换为搜索引擎使用的查询 (Query) 对象格式 , 这称为建立查询步骤 .
由于查询的对象可能很简单也可能很复杂, 因此 Lucene 提供了一个称之为 查询解析器的开发包,使用它可以根据通用查询语法将用户输入的文本处理成查询对象格式,这成为建立查询步骤 .
-
搜索查询 :
查询搜索索引并返回与查询语句匹配的文档,结果返回时按照查询的请求来进行排序 .
-
展现结果 :
一旦获得匹配查询语句并排好序的文档结果集,就为用户展现结果.
2 . 示例程序 :
2-0 . 准备环境 :
jar包 :
commons-io-2.6.jar 操作流(选用)
lucene-analyzers-common-8.1.1.jar (分析器包)
lucene-core-8.1.1.jar (Lucene核心包)
lucene-queryparser-8.1.1.jar (QueryParse查询包)
2-1 . 建立索引 :
// 创建索引
public class Indexer {
public static void main(String[] args) throws Exception {
// 指定索引库存放的位置(路径)
Directory directory = FSDirectory.open
(Paths.get("G:\\JAVA\\笔记\\LUCENE\\luceneData"));
// 也可以将索引库放在内存中
//Directory directoryMemory = new RAMDirectory();
// 创建一个标准的分词器
Analyzer analyzer = new StandardAnalyzer();
// 创建创建索引的配置类
// 将标准分词器配置进去
IndexWriterConfig config = new IndexWriterConfig(analyzer);
// 创建创建分词器对象
// 将分词器的配置对象放入
IndexWriter writer = new IndexWriter(directory, config);
// 创建原始文档的路径
File file = new File("G:\\JAVA\\笔记\\LUCENE\\LuceneSource");
File[] fileList = file.listFiles();
for (File file2 : fileList) {
// 获取文件的名称
String fileName = file2.getName();
// 获取文件的内容
String fileContent = FileUtils.readFileToString(file2);
// 获取文件的路径
String path = file2.getPath();
//创建文件的域
//参数一 : 域的名字
//参数二 : 域的值
//参数三 : 是否存储(内容是否以后要展示给用户)
Field fieldName = new TextField("file_name", fileName, Store.YES);
Field feildContent = new TextField("file_content",fileContent,Store.YES);
//不分析 不索引 只存储
Field feildPath = new StoredField("file_path",path);
//创建 document 对象
Document document = new Document();
document.add(fieldName);
document.add(feildContent);
document.add(feildPath);
//写入索引库
writer.addDocument(document);
}
writer.close();
}
}
运行结果 :
[外链图片转存失败(img-8odoK9Rc-1564795730645)(images/Snipaste_2019-06-24_13-44-40.png)]
2-2 . 搜索索引 :
//查询索引
public class Searcher {
public static void main(String[] args) throws Exception {
//创建索引库的文件夹
Directory directory = FSDirectory.open
(Paths.get("G:\\JAVA\\笔记\\LUCENE\\luceneData"));
//创建读取对象
IndexReader reader = DirectoryReader.open(directory);
//创建查询索引的对象
//将读取对象放入查询索引的对象中
IndexSearcher indexSearcher = new IndexSearcher(reader);
//封装查询条件
Query query = new TermQuery(new Term("file_name","apache"));
//进行查询 获得查询到的所有文档
TopDocs docs = indexSearcher.search(query , 10);
System.out.println("查询出来"+docs.totalHits+"条记录.");
//得到文档
ScoreDoc[] scoreDocs = docs.scoreDocs;
for (ScoreDoc scoreDoc : scoreDocs) {
//遍历文档信息
Document document = indexSearcher.doc(scoreDoc.doc);
System.out.println(document.get("file_name"));
System.out.println(document.get("file_content"));
System.out.println(document.get("file_path"));
}
reader.close();
}
}
[外链图片转存失败(img-4SCHeLjX-1564795730646)(images/Snipaste_2019-06-24_13-45-37.png)]
2-3 . IndexSearcher 常用 API :
方法 | 说明 |
---|---|
indexSearcher.search(query, n) | 根据Query搜索,返回评分最高的n条记录 |
indexSearcher.search(query, filter, n) | 根据Query搜索,添加过滤策略,返回评分最高的n条记录 |
indexSearcher.search(query, n, sort) | 根据Query搜索,添加排序策略,返回评分最高的n条记录 |
indexSearcher.search(booleanQuery, filter, n, sort) | 根据Query搜索,添加过滤策略,添加排序策略,返回评分最高的n条记录 |
2-4 . TopDocs :
方法或属性 | 说明 |
---|---|
totalHits | 匹配搜索条件的总记录数 |
scoreDocs | 顶部匹配记录 |
3 . 中文分词 :
3-1 . 分词器测试 :
/**
* 查看分词器的效果
* @author 15099
*
*/
public class TestAnalyzer {
public static void main(String[] args) throws Exception {
test();
}
/**
* 查看分词器的效果
* @throws IOException
*/
public static void test() throws IOException{
// 1. 创建标准的分词器
Analyzer analyzer = new StandardAnalyzer();
// 2. 获得 tokenStream 对象
// 参数一 : 域名 可以随便起
// 参数二 : 要解析的文字
TokenStream tokenStream = analyzer.tokenStream("test", "See the Get Started page for our tutorials that "
+ "offer a gentle introduction to messaging, one of the protocols RabbitMQ supports, "
+ "key messaging features, and some common usage scenarios. "
+ "AMQP 0-9-1 Overview provides a brief overview for the original RabbitMQ protocol.");
// 3. 获得每个关键词
CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class);
// 4. 获得每个关键词的起始和终止位置
OffsetAttribute offsetAttribute = tokenStream.addAttribute(OffsetAttribute.class);
// 5. 调整指针到列表头部
tokenStream.reset();
// 6. 遍历关键词列表 通过 incrementToken 方法判断是否遍历结束
while(tokenStream.incrementToken()){
// 关键词的起始地址
System.out.println("start:"+offsetAttribute.startOffset());
// 关键词的内容
System.out.println(charTermAttribute);
// 关键词的结束地址
System.out.println("end:"+offsetAttribute.endOffset());
}
}
}
效果如下 :
3-2 . Lucene自带的中文分词器 :
- StandardAnalyer : 标准的分词器, 将中文一个字一个字的拆分 即 : “你好啊” 拆分为 “你” “好” “啊” 三个.
- CJKAnalyer : 二分分词器 两个字两个字拆分 即 : “你好吖” 拆分为 “你好” “好吖” 两个
- 对中文支持较好,但扩展性差,扩展词库,禁用词库和同义词库等不好处理
通过三个分词器可以判断并不能满足我们的需要, 这就需要我们依赖第三方的中文分词器 .
3-3 . IK 分析器 :
3-3-1 . 介绍 :
IK 是以开源项目 Luence 为应用主体的,结合词典分词和文法分析算法的中文分词组件。从3.0版本开 始,IK发展为面向Java的公用分词组件,独立于Lucene项目,同时提供了对Lucene的默认优化实现。
3-3-2 . 使用 :
-
索引时候使用
当输入关键字进行搜索的时候,需要将关键字与文档中的域内容进行分析,因此需要对文档的域进行分析,分析的对象为文档的域, 当Field的属性tokenized(是否分词)为true时会对Field值进行分析.
-
搜索时候使用
当输入的关键字比较长的时候,进行搜索前也需要进行分析,分析完成再进行匹配 .
3-3-3 . 步骤 :
- 将 IKAnalyzer2012FF_u1.jar 导入项目中
- IKAnalyzer.cfg.xml 放入 classpath 下
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!--用户可以在这里配置自己的扩展字典
<entry key="ext_dict">ext.dic;</entry>
-->
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords">stopword.dic;</entry>
</properties>
- mydic.dic 放入 classpath 下
- stopword.dic 放入 classpath 下
- 进行编码
4 . 索引库的维护 :
4-1 . 索引库的添加 :
步骤 :
- 创建 IndexWriter 对象
- 创建 Document 对象
- 创建域到document对象
- 进行保存
// 增
@Test
public void testSave() throws Exception{
IndexWriter writer = this.getIndexWriter();
// 创建原始文档的路径
File file = new File("G:\\JAVA\\笔记\\LUCENE\\LuceneSource");
File[] fileList = file.listFiles();
for (File file2 : fileList) {
// 获取文件的名称
String fileName = file2.getName();
// 获取文件的内容
String fileContent = FileUtils.readFileToString(file2);
// 获取文件的路径
String path = file2.getPath();
//创建文件的域
//参数一 : 域的名字
//参数二 : 域的值
//参数三 : 是否存储(内容是否以后要展示给用户)
Field fieldName = new TextField("file_name", fileName, Store.YES);
Field feildContent = new TextField("file_content",fileContent,Store.YES);
//不分析 不索引 只存储
Field feildPath = new StoredField("file_path",path);
//创建 document 对象
Document document = new Document();
document.add(fieldName);
document.add(feildContent);
document.add(feildPath);
//写入索引库
writer.addDocument(document);
}
writer.close();
}
4-2 . 索引库的修改 :
// 改 (查询 + 添加)
public void testUpdate() throws Exception{
IndexWriter indexWriter = this.getIndexWriter();
Document document = new Document();
//向document对象中添加域。
//不同的document可以有不同的域,同一个document可以有相同的域。
document.add(new TextField("filename", "要更新的文档", Store.YES));
document.add(new TextField("content", " Lucene 是一个基于 Java 的全文信息检索工具包,它 不是一个完整的搜索应用程序,而是为你的应用程序提供索引和搜索功能。", Store.YES));
indexWriter.updateDocument(new Term("content", "java"), document);
//关闭indexWriter
indexWriter.close();
}
4-3 . 索引库的删除 :
- 全部删除 :
// 删
@Test
public void testDelete() throws Exception{
IndexWriter indexWriter = this.getIndexWriter();
indexWriter.deleteAll();
indexWriter.close();
}
- 根据条件删除 :
@Test
public void testDeleteByQuery() throws Exception{
IndexWriter indexWriter = this.getIndexWriter();
//删除名称有Apache的文档
Query query = new TermQuery(new Term("file_name","apache"));
indexWriter.deleteDocuments(query);
indexWriter.close();
}
4-4 . 索引库的查询 :
Lucene 查询时会将 Query 对象解析为类似于 SQL 的语句 .
Lucene 查询有两种方式 :
- 通过 Lucene 提供的 Query 子类
- 通过 Lucene 的 queryParse 对象 将用户输入的表达式转换为 Query 对象实例 .
4-4-1 . 通过 Query 子类查询 :
- 抽取出两个方法
IndexReader reader ;
//获得查询实例
public IndexSearcher getIndexSearch() throws Exception{
//创建索引库的文件夹
Directory directory = FSDirectory.open(Paths.get("G:\\JAVA\\笔记 \\LUCENE\\luceneData"));
//创建读取对象
reader = DirectoryReader.open(directory);
//创建查询索引的对象
//将读取对象放入查询索引的对象中
IndexSearcher indexSearcher = new IndexSearcher(reader);
return indexSearcher;
}
// 打印结果
public void printResult(IndexSearcher indexSearcher,Query query) throws Exception{
TopDocs docs = indexSearcher.search(query , 10);
System.out.println("查询出来"+docs.totalHits+"条记录.");
//得到文档
ScoreDoc[] scoreDocs = docs.scoreDocs;
for (ScoreDoc scoreDoc : scoreDocs) {
//遍历文档信息
Document document = indexSearcher.doc(scoreDoc.doc);
System.out.println(document.get("file_name"));
System.out.println(document.get("file_content"));
System.out.println(document.get("file_path"));
}
}
- 查询索引目录中的全部文档
// 查询所有文档
public void testQueryAll() throws Exception{
IndexSearcher indexSearch = this.getIndexSearch();
Query query = new MatchAllDocsQuery();
this.printResult(indexSearch, query);
}
- 根据条件查询文档
public void testQueryByFeild() throws Exception{
IndexSearcher indexSearcher = this.getIndexSearch();
Query query = new TermQuery(new Term("file_name","apache"));
TopDocs docs = indexSearcher.search(query , 10);
System.out.println("查询出来"+docs.totalHits+"条记录.");
//得到文档
ScoreDoc[] scoreDocs = docs.scoreDocs;
for (ScoreDoc scoreDoc : scoreDocs) {
//遍历文档信息
Document document = indexSearcher.doc(scoreDoc.doc);
System.out.println(document.get("file_name"));
System.out.println(document.get("file_content"));
System.out.println(document.get("file_path"));
}
reader.close();
}
4-4-2 . 通过 QueryParse 查询 :
通过QueryParser也可以创建Query,QueryParser提供一个Parse方法,此方法可以直接根据查询语法来查询。Query对象执行的查询语法可通过System.out.println(query);查询。需要使用到分析器。建议创建索引时使用的分析器和查询索引时使用的分析器要一致。
导入 lucene-queryparser-8.1.1.jar 包 .
// 通过 QueryParse 查询
@Test
public void testQueryParse() throws Exception{
IndexSearcher searcher = this.getIndexSearch();
QueryParser queryParser = new QueryParser("file_content", new StandardAnalyzer());
Query query = queryParser.parse("Lucene是java开发的");
printResult(searcher, query);
reader.close();
}
可以直接根据查询语法来查询。Query对象执行的查询语法可通过System.out.println(query);查询。需要使用到分析器。建议创建索引时使用的分析器和查询索引时使用的分析器要一致。
导入 lucene-queryparser-8.1.1.jar 包 .
// 通过 QueryParse 查询
@Test
public void testQueryParse() throws Exception{
IndexSearcher searcher = this.getIndexSearch();
QueryParser queryParser = new QueryParser("file_content", new StandardAnalyzer());
Query query = queryParser.parse("Lucene是java开发的");
printResult(searcher, query);
reader.close();
}