Luence
从百度百科中提及较多的关键字就可以得知,Luence是一个与检索、搜索、查询有关的工具。
从大家学习过的数据库举例大概了解一下什么是搜索的概念
普通的数据库查询:
查询sql_table表中所有数据:select * from sql_table;
查询id为668的信息条件的sql_table表中数据:select * from sql_table where id = '668';
常用查询算法
顺序扫描法
而数据库中使用的就是一种顺序扫描法,这种查询法通俗说就是:百度从一片一片文档中搜索,对于文档,进去一行一行搜索,对于每行进行每个词每个词对应要检索内容进行查找。
特点
①查询的准确率高
②查询的速度会随着数据的量增多而减慢。(Lucene诞生背景)
倒排索引法
类似于查新华字典的方式;字典首先将一个汉字根据部首进行拆分,例如汉→“氵”+ “又”。在查询时先查找到 “氵”,再查找“又”。倒排索引法也正是如此,将数据的内容(数据库、网页web、磁盘文件等)提取(jdbc、爬虫、IO流)成文档,再将文档通过指定分词器的特定分词方式将文档分成多个词组成索引,存放在索引库中。
在查找时通过匹配索引,使用索引定位到这篇文章是哪篇文档。
Lucene会对文档建立倒排索引
1.提取资源中关键信息,建立索引(目录)
2.搜索时,根据关键字,找到资源的位置
查询时先查询索引,通过索引找文档的过程叫做全文检索。
切分词:将一句一句话切分成一个一个词,去掉停用词(的,地,得,a,an,the等),去掉空格、标点符号,大写字母转换成小写字母,去掉重复的词。
特点
①查询准确率高
②查询速度快,不会因为查询内容量增加,而使查询速度逐渐变慢
③索引文件会占用额外的磁盘空间,占用磁盘量会增大
Lucene全文检索的流程
索引和搜索流程
索引过程
对要搜索的原始内容(包括web网页,数据库,磁盘上文件)进行索引构建一个索引库
- 获得文档
- 创建文档
- 分析文档
- 索引文档
搜索过程
从索引库中搜索内容
- 创建查询
- 执行搜索,从索引库搜索
- 渲染搜索结果
入门案例
创建的是一个maven项目
pom.xml依赖及配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>LuenceDemo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<skipTests>true</skipTests>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<version>7.7.2</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-common</artifactId>
<version>7.7.2</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-queryparser</artifactId>
<version>7.7.2</version>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- mysql数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
<!-- IK中文分词器 -->
<dependency>
<groupId>org.wltea.ik-analyzer</groupId>
<artifactId>ik-analyzer</artifactId>
<version>8.1.0</version>
</dependency>
<!--web起步依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 引入thymeleaf -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- Json转换工具 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.51</version>
</dependency>
</dependencies>
</project>
Sku类(数据库字段 + get/set方法) + Dao接口以及实现类
package cn.itheima.pojo;
public class Sku {
//商品主键id
private String id;
//商品名称
private String name;
//价格
private Integer price;
//库存数量
private Integer num;
//图片
private String image;
//分类名称
private String categoryName;
//品牌名称
private String brandName;
//规格
private String spec;
//销量
private Integer saleNum;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getPrice() {
return price;
}
public void setPrice(Integer price) {
this.price = price;
}
public Integer getNum() {
return num;
}
public void setNum(Integer num) {
this.num = num;
}
public String getImage() {
return image;
}
public void setImage(String image) {
this.image = image;
}
public String getCategoryName() {
return categoryName;
}
public void setCategoryName(String categoryName) {
this.categoryName = categoryName;
}
public String getBrandName() {
return brandName;
}
public void setBrandName(String brandName) {
this.brandName = brandName;
}
public String getSpec() {
return spec;
}
public void setSpec(String spec) {
this.spec = spec;
}
public Integer getSaleNum() {
return saleNum;
}
public void setSaleNum(Integer saleNum) {
this.saleNum = saleNum;
}
}
package cn.itheima.dao;
import cn.itheima.pojo.Sku;
import java.util.List;
public interface SkuDao {
/***
* 查询所有的Sku数据
* *
* @return **/
public List<Sku> querySkuList();
}
package cn.itheima.dao;
import cn.itheima.pojo.Sku;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
public class SkuDaoImpl implements SkuDao{
@Override
public List<Sku> querySkuList() {
// 数据库链接
Connection connection = null;
// 预编译statement
PreparedStatement preparedStatement = null;
// 结果集
ResultSet resultSet = null;
// 商品列表
List<Sku> list = new ArrayList<Sku>();
try {
// 加载数据库驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 连接数据库
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/lucene", "root", "123456");
// SQL语句
String sql = "SELECT * FROM tb_sku";
// 创建preparedStatement
preparedStatement = connection.prepareStatement(sql);
// 获取结果集
resultSet = preparedStatement.executeQuery();
// 结果集解析
while (resultSet.next()) {
Sku sku = new Sku();
sku.setId(resultSet.getString("id"));
sku.setName(resultSet.getString("name"));
sku.setSpec(resultSet.getString("spec"));
sku.setBrandName(resultSet.getString("brand_name"));
sku.setCategoryName(resultSet.getString("category_name"));
sku.setImage(resultSet.getString("image"));
sku.setNum(resultSet.getInt("num"));
sku.setPrice(resultSet.getInt("price"));
sku.setSaleNum(resultSet.getInt("sale_num"));
list.add(sku);
}
} catch (Exception e) {
e.printStackTrace();
}
return list;
}
}
创建索引测试
package cn.itheima.test;
import cn.itheima.dao.SkuDao;
import cn.itheima.dao.SkuDaoImpl;
import cn.itheima.pojo.Sku;
import org.apache.catalina.Store;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.*;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.Term;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.junit.Test;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
/**
* @className: TestIndex
* @description: TODO 类描述
* @author: niaonao
* @date: 2022/1/4
**/
public class TestIndex {
/*
* 创建索引库
* */
@Test
public void createIndexTest() throws IOException {
//1.采取数据
SkuDao skuDao = new SkuDaoImpl();
List<Sku> skuList = skuDao.querySkuList();
//创建文档集合
List<Document> documentList = new ArrayList<Document>();
for(Sku sku:skuList){
//2.创建文档对象
Document document = new Document();
//创建域并将域放入到文档对象中
//第一个参数表示域名(Field),第二个参数表示域值,第三个参数表示是否存储
document.add(new TextField("id",sku.getId(), Field.Store.YES));
document.add(new TextField("name",sku.getName(), Field.Store.YES));
document.add(new IntPoint("price",sku.getPrice(), Field.Store.YES));
document.add(new StoredField("price",sku.getPrice(), Field.Store.YES));
document.add(new TextField("num",String.valueOf(sku.getNum()), Field.Store.YES));
document.add(new StoredField("image",sku.getImage(), Field.Store.YES));
document.add(new StringField("categoryName",sku.getCategoryName(), Field.Store.YES));
document.add(new TextField("brandName",sku.getBrandName(), Field.Store.YES));
document.add(new StoredField("spec",sku.getSpec(), Field.Store.YES));
document.add(new StoredField("saleNum",String.valueOf(sku.getSaleNum())));
//将文档对象放入到文档集合中
documentList.add(document);
}
/*3.创建分词器对象,standardAnalyzer标准分词器,对英文效果好;对中文是单字分词,一个字认为一个词*/
Analyzer analyzer = new StandardAnalyzer();
//4.创建Directory目录,目录对象表示索引库的位置
Directory dir = FSDirectory.open(Paths.get("E:\\Lucene-indexCool"));
//5.创建IndexWriterConfig对象,指定切分词使用的分词器
IndexWriterConfig config = new IndexWriterConfig(analyzer);
//6.创建IndexWriter输出流对象,指定输出的位置和使用的config初始化对象
IndexWriter indexWriter = new IndexWriter(dir,config);
//7.写入文档到索引库
for(Document doc:documentList){
indexWriter.addDocument(doc);
}
//8.释放资源
indexWriter.close();
}
搜索测试
public class TestSearch {
/**
* 文本检索
*/
@Test
public void testIndexSearch() throws ParseException, IOException {
//1.创建分词器(对搜索的关键词进行分词使用)
//注意:搜索的分词器和索引创建的分词器要一模一样
// 使用得是标准分词器(Lucene原生自带)
Analyzer analyzer = new StandardAnalyzer();
//2.创建查询对象
//第一个参数:默认查询域,如果查询的关键字中带搜索的域名,则从指定的域中查询;反之默认搜索域中查询
//第二个参数:使用的分词器
QueryParser queryParser = new QueryParser("name",analyzer);
//3.设置搜索关键词
Query query = queryParser.parse("华为手机");
//4.创建Direcotyr目录对象,指定索引库的位置
Directory dir = FSDirectory.open(Paths.get("E:\\Lucene-indexCool"));
//5.创建输入流对象
IndexReader indexReader = DirectoryReader.open(dir);
//6.创建搜索对象
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
//7.搜索,并返回结果
//第二个参数:返回多少条数据用于展示,分页使用
TopDocs topDocs = indexSearcher.search(query,10);
//获取查询到的结果集的总数,打印
System.out.println("=========count==========" + topDocs.totalHits);
//8.获取结果集
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
//9.遍历结果集
if(scoreDocs != null){
for(ScoreDoc scoreDoc : scoreDocs){
//获取查询到的文档唯一标识,文档id,这个id是lucene再创建文档时自动分配的
int docID = scoreDoc.doc;
//通过文档id,读取文档
Document doc = indexSearcher.doc(docID);
System.out.println("=====================");
//通过域名,从文档中获取域值
System.out.println("==id===" + doc.get("id"));
System.out.println("==image===" + doc.get("image"));
System.out.println("==name===" + doc.get("name"));
System.out.println("==price===" + doc.get("price"));
System.out.println("==num===" + doc.get("num"));
}
}
}