》
官网 http://lucene.apache.org/
下载地址:https://mirrors.tuna.tsinghua.edu.cn/apache/lucene/java/7.5.0/
》
Lucene的全文检索是指什么:
程序扫描文档,对文档document建立索引,并对文档进行分词 ik,对每一个词建立索引并关联文档document的编号(索引);
当用户进行搜索时候,按照搜索条件的分词的索引,匹配出对应的文档索引,把具体的文档展现出来,整个过程就是全文索引(倒排索引基本原理);
->
->全文检索的特点:
1、只处理文本不处理语义
2、搜索时英文不区分大小写
3、结果列表有关度 排行
》
1、全文检索和数据库模糊查询(或者模糊匹配)的区别:
1、数据库模糊匹配会查询出许多和期望无关的数据;
=>假设搜索it相关的:
=>select * from db where name like ‘%’ it ‘%’ 会搜索出git ,显然结果并不是我们所期望的;
2、相关度排序问题数据库无法解决;
3、数据库模糊匹配效率较低;
》
2、Lucene与Solr的关系
/
->Lucene: 是全文检索的底层API;
->Solr: 基于Lucene开发的搜索服务器;
》
3、使用Lucene需要的依赖:
<dependencies>
<!-- Junit单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- lucene核心库 -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<version>4.10.2</version>
</dependency>
<!-- Lucene的查询解析器 -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-queryparser</artifactId>
<version>4.10.2</version>
</dependency>
<!-- lucene的默认分词器库 -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-common</artifactId>
<version>4.10.2</version>
</dependency>
<!-- lucene的高亮显示 -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-highlighter</artifactId>
<version>4.10.2</version>
</dependency>
<!--ik分词器 -->
<dependency>
<groupId>com.janeluo</groupId>
<artifactId>ikanalyzer</artifactId>
<version>2012_u6</version>
</dependency>
</dependencies>
》>1、创建测试类完成一次分词:
1、创建文档,添加 StringField、TextField
2、对文档分词:使用 IKAnalyzer 分词器
- FSDirectory创建目录
- DirectoryReader 读取Directory
- 搜索工具IndexSearcher
3、创建写的工具:
- 配置IndexWriterConfig写的配置,设置规则
- 生成写的对象IndexWriter
4、提交关闭
- commit
- close
》
以下测试源码分享地址:
https://github.com/medoo-Ai/lucene
/**
* @auther SyntacticSugar
* @data 2018/11/23 0023上午 11:37
*/
public class CreateIndexTest {
/**
* 给 文档添加fieldName /FieldText/store 是yes or no 控制是否保存
*/
@Test
public void createIndexTest() {
Document document = new Document();
document.add(new StringField("id", "1", Field.Store.YES));
document.add(new TextField("title", "谷歌地图之父跳槽facebook厉害了我的哥碉堡了" +
"蓝瘦香菇", Field.Store.YES));
// 创建储存目录、分词
try {
FSDirectory indexDir = FSDirectory.open(new File("indexDir"));
// StandardAnalyzer analyzer = new StandardAnalyzer();
IKAnalyzer analyzer = new IKAnalyzer();
/**
* 创建文档配置对象,IndexWriterConfig.OpenMode.CREATE 清空创建索引
* IndexWriterConfig.OpenMode.APPEND 在原来基础上追加
*/
IndexWriterConfig indexWriterConfig = new IndexWriterConfig(Version.LATEST, analyzer);
indexWriterConfig.setOpenMode(IndexWriterConfig.OpenMode.CREATE);
//索引
IndexWriter indexWriter = new IndexWriter(indexDir, indexWriterConfig);
//
//写入索引,提交,关闭
indexWriter.addDocument(document);
indexWriter.commit();
indexWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
》生成如下indexDir目录,目录内是分词;
》2、建新测试,添加多个document
文档document中分词,可以添加集合list
并且 ,创建文档配置对象 indexWriterConfig 时候:
- IndexWriterConfig.OpenMode.CREATE 清空创建索引
- IndexWriterConfig.OpenMode.APPEND 在原来基础上追加
/**
* 给 文档添加filedName /FiledText/store 是yes or no
*/
@Test
public void createIndexTest2() throws IOException {
ArrayList<Document> documents = new ArrayList<>();
Document document1 = new Document();
document1.add(new StringField("id", "1", Field.Store.YES));
document1.add(new TextField("title", "谷歌地图之父跳槽facebook厉害了我的哥碉堡了" +
"蓝瘦香菇", Field.Store.YES));
Document document2 = new Document();
document2.add(new StringField("id", "2", Field.Store.YES));
document2.add(new TextField("title", "谷歌地图之父拉斯加盟社交网站Facebook" +
"蓝瘦香菇", Field.Store.YES));
//添加到集合中
documents.add(document1);
documents.add(document2);
// 创建储存目录、分词
FSDirectory indexDir = FSDirectory.open(new File("indexDir"));
// StandardAnalyzer analyzer = new StandardAnalyzer();
IKAnalyzer analyzer = new IKAnalyzer();
/**
* 创建文档配置对象,IndexWriterConfig.OpenMode.CREATE 清空创建索引
* IndexWriterConfig.OpenMode.APPEND 在原来基础上追加
*/
IndexWriterConfig indexWriterConfig = new IndexWriterConfig(Version.LATEST, analyzer);
indexWriterConfig.setOpenMode(IndexWriterConfig.OpenMode.CREATE);
//索引
IndexWriter indexWriter = new IndexWriter(indexDir, indexWriterConfig);
//
//写入索引,提交,关闭s
indexWriter.addDocuments(documents);
indexWriter.commit();
indexWriter.close();
}
》3、新建测试类,使用分词查询 “谷歌地图之父” ,为多条件查询
3、Query查询:
步骤如下:
1、创建目录FSDirectory 、创建索引读取工具DirectoryReader、创建索引搜索工具indexSearcher
2、查询解析器:QueryParser parser
- 解析要查询的 parse(String query)生成query对象
3、使用搜索工具indexSearcher进行查询,把query对象传参进去,返回topDocs文档对象
- topDocs包含totalHits查询的总数目、scoreDocs分数
- 对scoreDocs 进行遍历,获取每一个文档的 编号docId 、得分
- 通过搜索工具 indexSearcher对文档docId 编号搜索,找到具体的文档对象 document
4、document文档对象中,包含id / title 两部分,get取出
- String id = doc.get(“id”);
- String title = doc.get(“title”);
对于查询解析器 parse,源码如下:
@Test
public void testSearch() throws IOException, ParseException {
FSDirectory indexDir = FSDirectory.open(new File("indexDir"));
/**
* 索引读取工具
* 索引搜索工具
*/
IndexReader open = DirectoryReader.open(indexDir);
IndexSearcher indexSearcher = new IndexSearcher(open);
//创建查询解析器[可以传参入一个数组,多条件进行查询],创建要查询的对象
// QueryParser parser = new QueryParser("title", new IKAnalyzer());
QueryParser parser = new MultiFieldQueryParser(new String[]{"id", "title"}, new IKAnalyzer());
Query query = parser.parse("谷歌地图之父");
//搜索
//topDocs包含totalHits、scoreDocs
TopDocs topDocs = indexSearcher.search(query, 5);//参数5,就是排名前五名的
System.out.println("查找到数据的条数" + topDocs.totalHits);
/**
* 获取文档编号中文档的编号以及文档的得分
*/
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
for (ScoreDoc scoreDoc : scoreDocs) {
int docId = scoreDoc.doc;
float score = scoreDoc.score;
//根据文档编号查找 doc
Document doc = indexSearcher.doc(docId);
String id = doc.get("id");
String title = doc.get("title");
System.out.println(id + ":" + title);
}
}
》抽取search查询的工具类;
》
经常要用:
抽取了一个query查询的工具类:
public void search(Query query) throws IOException {
//建新目录,读取工具,搜索工具
FSDirectory directory = FSDirectory.open(new File("indexDir"));
DirectoryReader reader = DirectoryReader.open(directory);
IndexSearcher searcher = new IndexSearcher(reader);
/**
* 搜索符合条件的topDocs
* 对文档进行解析 获取 totalHits ,scoreDocs
*/
TopDocs topDocs = searcher.search(query, 10);
System.out.println("本次搜索到的条数:" + topDocs.totalHits);
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
//遍历
for (ScoreDoc scoreDoc : scoreDocs) {
int docId = scoreDoc.doc;
float score = scoreDoc.score;
//查找到对应的文档
Document doc = searcher.doc(docId);
System.out.println(doc.get("id") + ":" + doc.get("title") + ":" + score);
}
}
》词条查询:
/**
* testTermQuery 词条查询
*/
@Test
public void testTermQuery() throws IOException {
// 创建词条查询的对象
TermQuery title = new TermQuery(new Term("title", "谷歌"));
//调用查询方法进行查询
search(title);
}
/**
* WildcardQuery 通配符 ? 查询
*/
@Test
public void WildcardQuery() throws IOException {
TermQuery query = new TermQuery(new Term("title", "??"));
search(query);
}
》通配符查询:
testFuzzyQuery 通配符查询、 Fuzzy:英文有 :失真、模糊 的意思
- 词条中并没有 “谷歌啊” 这样的词条,却查出来信息,因为模糊设置了编辑距离(容错);
- 编辑距离 0-2
/**
* testFuzzyQuery 通配符查询 Fuzzy:失真、模糊
* fscevool 编辑距离0-2
*/
@Test
public void testFuzzyQuery() throws IOException {
FuzzyQuery query = new FuzzyQuery(new Term("title", "谷歌啊"), 1);
search(query);
}
》组合查询:
1、NumericRangeQuery 数字查询范围:
- 可以进行精确的查找
- 参数:字段名称,最小值、最大值、是否包含最小值、是否包含最大值
2、 BooleanQuery(组合查询)
创建组合查询对象query,把查询对象query1,query2分别添加到query中
@Test
public void testNumericRangeQuery() throws IOException {
//Query接口
Query query = NumericRangeQuery.newLongRange("id", 2L, 2L, true, true);
search(query);
}
/**
* BooleanQuery(组合查询)
* Occur.MUST_NOT 不是必须的
* Occur.SHOULD 必须的
*/
@Test
public void testBooleanQuery() throws IOException {
Query query1 = NumericRangeQuery.newLongRange("id", 2L, 2L, true, true);
Query query2 = NumericRangeQuery.newLongRange("id", 2L, 4L, true, true);
//创建boolean查询,然后添加query1/query2
BooleanQuery query = new BooleanQuery();
query.add(query1, BooleanClause.Occur.MUST_NOT);
query.add(query2, BooleanClause.Occur.SHOULD);
search(query);
}
》修改,删除
/**
* 修改索引
* 先删除,后创建
* 1、所以我们是修改 一把针对唯一的进行修改
* 2、deleteDocuments 若id 是数字,直接删除即可
*/
@Test
public void testUpdate() throws IOException {
//创建目录,创建IndexWriterConfig,创建索引写出对象
FSDirectory directory = FSDirectory.open(new File("indexDir"));
IndexWriterConfig config = new IndexWriterConfig(Version.LATEST, new IKAnalyzer());
IndexWriter indexWriter = new IndexWriter(directory, config);
//创建文档索引数据,而后进行修改
Document document = new Document();
document.add(new StringField("id", "1", Field.Store.YES));
document.add(new TextField("title", "谷歌地图之父跳槽facebook 为了加入复仇者联盟 屌爆了啊", Field.Store.YES));
/**
* 修改
* Term 词条
* document 文档
*/
indexWriter.updateDocument(new Term("id", "1"), document);
indexWriter.commit();
indexWriter.close();
}
/**
* testDelete
* 删除词条 ,通过id 来删除 indexWriter.deleteDocuments(new Term("id", "1"));
* 1、删除所有 deleteAll
* 2、条件删除 query来删除
*/
@Test
public void testDelete() throws IOException {
//创建目录,创建IndexWriterConfig,创建索引写出对象
FSDirectory directory = FSDirectory.open(new File("indexDir"));
IndexWriterConfig config = new IndexWriterConfig(Version.LATEST, new IKAnalyzer());
IndexWriter indexWriter = new IndexWriter(directory, config);
//创建文档索引数据
// indexWriter.deleteDocuments(new Term("id", "1"));
Query query = NumericRangeQuery.newLongRange("id", 2L, 2L, true, true);
indexWriter.deleteDocuments(query);
indexWriter.commit();
indexWriter.close();
}
》代码高亮显示:
完成代码高亮显示;
主要使用了格式化器和高亮工具:
- 代码高亮工具构造器:public Highlighter(Formatter formatter, Scorer fragmentScorer) , 其中fragmentScorer 是切面评分,也就是queryScorer,对显示的内容进行一个评分(因为要显示的东西太多了,不能全部都高亮的吧,通过queryScorer来确定);
- 格式化器SimpleHTMLFormatter 作为参数传递,指定了格式,本例中采用斜体 ;
@Test
public void testHighlighter() throws Exception {
// 目录,directoryreader ,searcher
FSDirectory directory = FSDirectory.open(new File("indexDir"));
DirectoryReader reader = DirectoryReader.open(directory);
IndexSearcher searcher = new IndexSearcher(reader);
//分词
QueryParser parser = new QueryParser("title", new IKAnalyzer());
Query query = parser.parse("谷歌地图");
searcher.search(query, 10);
/**
* 格式化器:
* 准备高亮工具:
*/
SimpleHTMLFormatter formatter = new SimpleHTMLFormatter("<em>", "</em>");
QueryScorer queryScorer = new QueryScorer(query);
Highlighter highlighter = new Highlighter(formatter, queryScorer);
//搜索文档数据
TopDocs topDocs = searcher.search(query, 10);
System.out.println("本次搜索到的条数:" + topDocs.totalHits);
//bianli
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
for (ScoreDoc scoreDoc : scoreDocs) {
// 获取文档编号,得分编号docId / score
int docId = scoreDoc.doc;
float score = scoreDoc.score;
//查询文档的 title 、得分
Document doc = searcher.doc(docId);
String title = doc.get("title");
// 高亮处理 Analyzer analyzer, String fieldName, String text
String bestFragment = highlighter.getBestFragment(new IKAnalyzer(), "title", title);
System.out.println("高亮显示:" + bestFragment);
//打印得分到控制台
System.out.println(score);
}
}
》排序:
排序:
创建排序对象 ,搜索
默认降序
@Test
public void testSortQuery() throws Exception {
// 目录对象,目录读取,搜索工具
FSDirectory directory = FSDirectory.open(new File("indexDir"));
DirectoryReader reader = DirectoryReader.open(directory);
IndexSearcher searcher = new IndexSearcher(reader);
//分词,创建查询对象 query
QueryParser parser = new QueryParser("title", new IKAnalyzer());
Query query = parser.parse("谷歌地图");
/**
* 排序对象、查询
* 排序字段参数,默认降序 String field, SortField.Type type, boolean reverse)
*/
Sort sort = new Sort(new SortField("", SortField.Type.LONG, true));
TopDocs topDocs = searcher.search(query, 1, sort);
//
//展示 topDocs文档中的数据
System.out.println("共查询到数据条数:" + topDocs.totalHits);
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
//遍历
for (ScoreDoc scoreDoc : scoreDocs) {
int docId = scoreDoc.doc;
Document doc = searcher.doc(docId);
System.out.println("文档编号:" + doc.get("id") + ":" + doc.get("title") + "得分:" + scoreDoc.score);
}
}
运行下下;