搜索引擎--Lucene
前言:
1.搜索技术:
1.1 搜索引擎的种类
搜索引擎按照功能通常分为垂直搜索和综合搜索
①垂直搜索是指专门针对某一类信息进行搜索
②综合搜索是指对众多信息进行综合性的搜索
1.2 倒排索引
倒排索引又称为反向索引,有id到关键词的映射变为有关键词到ID的映射,加快了查找的效率
Lucene:
Lucene是一套用于全文检索和搜寻的开源程序库,由Apache软件基金会支持和提供
Lucene提供了一个简单而强大的应用程序API,能够做全文索引和搜寻,在Java开发环境里Lucene是一个成熟的免费开放源代码的工具
Lucene不是现成的搜索引擎产品,但是可以用来制作搜索引擎产品
全文检索:
计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式。
Lucene全文检索就是对文档中全部内容进行分词,然后对所有单词建立倒排索引的过程。
Lucene下载的网址:https://www.apache.org/
Lucene的基本使用
使用lucene的api来实现对索引的增(创建索引)、删(删除索引)、改(修改索引)、查(搜索数据)
①创建索引
创建索引过程中涉及的一些名词:
文档Document:数据库中一条具体的记录
字段Field:数据库中的每个字段
目录对象Directory:物理存储位置
写出器的配置对象:需要分词器和Lucene的版本
对应的jar包:
<!-- 添加Lucene全文检索 --> <!-- lucene核心库 --> <dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-core</artifactId> <version>8.6.2</version> </dependency> <!-- lucene的查询解析器 --> <dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-queryparser</artifactId> <version>8.6.2</version> </dependency> <!-- lucene的默认的分词器库 --> <dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-analyzers-common</artifactId> <version>8.6.2</version> </dependency> <!-- lucene的高亮显示 --> <dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-highlighter</artifactId> <version>8.6.2</version> </dependency>
Lucene-core中自带的分词器有下面几种:(没有IKAnalyzer),对中文的支持都不怎么好,所以我们需要进入新的分词器:IK分词器
<!-- ik分词器 --> <dependency> <groupId>com.github.magese</groupId> <artifactId>ik-analyzer</artifactId> <version>8.3.0</version> </dependency>
注意:IK分词器jar包和Lucene的jar包的版本匹配,以及Lucene和jdk版本之前匹配的问题,自己尝试的时候,因为jar包问题就浪费好多时间
Lucene6+版本就需要使用JDK1.8了
1.创建索引
代码实现来创建索引:
/** * 使用Lucene创建索引 * @author xiaoxu * * 步骤: * 1.创建文档对象 * 2.创建存储目录 * 3.创建分词器 * 4.创建索引写入器的配置对象 * 5.创建索引写入器对象 * 6.将文档交给索引写入器 * 7.提交 * 8.关闭 * */ public class CreateLuceneIndex { private static final Logger LOG = LoggerFactory.getLogger(CreateLuceneIndex.class); private static final int SIZE = 1000; private static final String INDEX_PATH = "G:\\Lucene\\luceneDir"; //创建索引 public static void createIndex() throws Exception{ //获取数据的总条数 int count = KqMbSessionUtil.selectOne("luceneMapper.getRegnZbDataCount"); LOG.info("需要创建索引的数据条数为{}条",count); while(count>0) { //从数据库中获取需要创建索引的数据集合 List<Map<String,Object>> dataList = KqMbSessionUtil.selectList("luceneMapper.getNeedCreateIndexData",SIZE); //创建文档的集合 List<Document> docs = new ArrayList<>(); for(Map<String,Object> djbDataMap:dataList) { //创建文档对象 Document document = new Document(); //创建并添加字段信息。参数:字段的名称、字段值、是否存储,yes代表存储 document.add(new StringField("gid", Convert.toStr(djbDataMap.get("gid")), Field.Store.YES)); //zl使用TextField,即创建索引又会被分词。StringField会创建索引,但不会分词 document.add(new TextField("zl", Convert.toStr(djbDataMap.get("zl")), Field.Store.YES)); docs.add(document); } //索引目录类,指定索引在硬盘中的位置 Directory directory = FSDirectory.open(Paths.get(INDEX_PATH)); //引入IK分词器 Analyzer analyzer = new IKAnalyzer(); //analyzer.setVersion(Version.LATEST); //索引写出工具的配置对象 IndexWriterConfig config = new IndexWriterConfig(analyzer); //设置打开方式:OpenMpde.APPEND 会在索引库的基础上追加新的索引;OpenMode.CREATE会先清空原来数据,再提交新的索引 //第一次执行时因为没有索引库和索引文件,所以append会报错,可以先使用create commit之后在执行,或者使用CREATE_OR_APPEND config.setOpenMode(IndexWriterConfig.OpenMode.APPEND); //创建索引的写出工具类。参数:索引目录和配置信息 IndexWriter indexWriter = new IndexWriter(directory, config); //把文档集合交给IndexWriter(索引写入器) indexWriter.addDocuments(docs); //提交 indexWriter.commit(); //关闭 indexWriter.close(); KqMbSessionUtil.update("luceneMapper.updateLuceneIndex", dataList); } } }
结合实际情况,我需要给我这边坐落的字段添加全文索引,但是数据库中存在的数据已经是亿级了,这里就批量的创建索引了。
创建索引的API讲解
Document:文档对象
相当于数据库中的一条记录,有id和content字段等。
Field:字段类
一个Document中可以有很多不同的字段,每个字段都是一个Field类的对象。
一个Document中对应的字段其类型是不确定的,因此Field类提供了各种不同的子类,来对应这些字段,如DoubleField、FloatField、StoredField、 TextField、StringField等等。
字段类型的区别如下:
TextField:一定会被索引,一定会分词;
StoredField:一定会存储,一定不会被索引;
DoubleField、FloatField、IntField、StringField等:一定会索引,但是一定不会分词;
Directory:目录类
指定索引要存放的位置
有2中子类来确定索引存在的方式:
①FSDirectory:文件系统目录,会把索引指向本地磁盘;特点:速度略慢,但是比较安全
②RAMDirectory:内存目录,会把索引库保存在内存中;特点:速度块,但是不安全
Analyzer:分词器
因为Lucene自带的分词器对中文的支持不是太好,所以引入了IK分词器,即IKAnalyzer
IndexWriterConfig:索引写出器配置类
①加载引入的分词器
//索引写出工具的配置对象 IndexWriterConfig config = new IndexWriterConfig(analyzer)
②设置往索引库中添加的方式
//设置打开方式:OpenMpde.APPEND 会在索引库的基础上追加新的索引;OpenMode.CREATE会先清空原来数据,再提交新的索引 //第一次执行时因为没有索引库和索引文件,所以append会报错,可以先使用create commit之后在执行,或者使用CREATE_OR_APPEND config.setOpenMode(IndexWriterConfig.OpenMode.APPEND);
IndexWriter:索引写出器类
加载索引写出器配置类,是索引写出工具,实现对索引的增删改查操作
//创建索引的写出工具类。参数:索引目录和配置信息 IndexWriter indexWriter = new IndexWriter(directory, config); //把文档集合交给IndexWriter(索引写入器) indexWriter.addDocuments(docs); //提交 indexWriter.commit(); //关闭 indexWriter.close();
2.查询索引数据
代码实现:
/** * 查询索引数据 * 1.创建读取目录对象 * 2.创建索引读取工具 * 3.创建索引搜索工具 * 4.创建查询解析器 * 5.创建查询对象 * 6.搜索数据 * 7.对数据的各种操作 */ public static void queryLuceneIndex() throws Exception{ //索引目录对象 Directory directory = FSDirectory.open(Paths.get(INDEX_PATH)); //索引读取工具 IndexReader indexReader = DirectoryReader.open(directory); //索引搜索工具 IndexSearcher indexSearcher = new IndexSearcher(indexReader); //创建查询解析器(2个参数:查询字段的名称,分词器) QueryParser queryParser = new QueryParser("zl", new IKAnalyzer()); //创建查询对象 Query query = queryParser.parse("官渡区"); //搜索数据,2个参数:查询条件对象;查询的最大结果条数 TopDocs topDocs = indexSearcher.search(query, 10); //获取总条数 long total = topDocs.totalHits.value; System.out.println("本次搜索共找到" + total + "条数据"); //获取得分文档对象(ScoreDoc)数组,ScoreDoc中包含:文档的编号,文档的得分 ScoreDoc [] scoreDocs = topDocs.scoreDocs; for(ScoreDoc score:scoreDocs) { //取出文档编号 int docId = score.doc; //根据编号去找文档 Document doc = indexReader.document(docId); System.out.println("id="+doc.get("id")+"*******zl="+doc.get("zl")+"*********得分="+score.score); } }
查询索引的核心API
QueryParser:查询解析器
//创建查询解析器(2个参数:查询字段的名称,分词器) QueryParser queryParser = new QueryParser("zl", new IKAnalyzer()); //创建查询对象 Query query = queryParser.parse("官渡区");
MultiFieldQueryParser:多字段的查询解析器
Query:查询对象,包含要查询的关键词信息
query中包含很多子类,可以直接创建查询对象,实现高级查询
IndexSearch:索引搜索对象,执行搜索功能
IndexSearch可以帮助我们实现快速搜索、排序、打分等功能
IndexSearch需要依赖IndexReader类
TopDocs:查询结果对象
通过IndexSearch对象搜索获得的结果就是TopDocs对象
在TopDocs中,包括两部分信息:
totalHits:查询到的总条数
ScoreDoc[]:得分文档对象的数组
ScoreDoc:得分文档对象
ScoreDoc是得分文档对象,包含两部分数据:
int doc:文档编号
float score:文档的得分信息
拿到编号后,可以根据编号获取文档的信息
特殊查询
①TermQuery:词条查询
词条:就是数据分词后得到的每一个词,是分词的最小单位,不能继续分。
场景:如果一个字段不需要分词的,那么我们一般使用词条查询,例如:id
Lucene中,Term要求字段的类型必须是字符串
//索引目录对象 Directory directory = FSDirectory.open(Paths.get(INDEX_PATH)); //索引读取工具 IndexReader indexReader = DirectoryReader.open(directory); //索引搜索工具 IndexSearcher indexSearcher = new IndexSearcher(indexReader); //创建查询解析器(2个参数:查询字段的名称,分词器) //QueryParser queryParser = new QueryParser("zl", new IKAnalyzer()); //创建查询对象 //Query query = queryParser.parse("官渡区"); Query query = new TermQuery(new Term("zl", "官渡区")); //搜索数据,2个参数:查询条件对象;查询的最大结果条数 TopDocs topDocs = indexSearcher.search(query, 10);
②WildcardQuery:通配符查询
测试通配符查询
? 代表任意一个字符
* 代表任意多个字符
Query query = new WildcardQuery(new Term("zl", "官*区")); //搜索数据,2个参数:查询条件对象;查询的最大结果条数 TopDocs topDocs = indexSearcher.search(query, 10);
②FuzzyQuery:模糊查询
//创建模糊查询对象:允许用户输错,但是要求错误的最大编辑距离不能超过2 //编辑距离:一个单词到另一个单词最少要修改的次数 facebool ---> facebook 需要编辑1次 Query query = new FuzzyQuery(new Term("title", "fscevool")); //可以手动指定编辑距离,但是参数必须在0-2之间 Query query = new FuzzyQuery(new Term("title", "fscevool"),1);
③NumericRangeQuery:数值范围查询
/** * *注意:数值范围查询,可以用来对非String类型的ID进行精确查找 */ //数值范围查询对象,参数:字段名称、最小值、最大值、是否包含最小值、是否包含最大值 Query query = NumericRangeQuery.newLongRange("id",2L,2L,true,true);
另外还有组合查询等等,需要用到的可以自行探索
3.修改索引
对应的代码如下:
/** * 修改索引 * * 1.创建文档存储目录 * 2.创建索引写入器配置对象 * 3.创建索引写入器 * 4.创建文档数据 * 5.修改 * 6.提交 * 7.关闭 * * 注意: * A:Lucene修改功能底层会先删除,再把新的文档添加 * B:修改功能会根据Term进行匹配,所有匹配到的都会被删除,这样不好 * C:因此,我们修改时,都会根据一个唯一不重复字段进行匹配修改,例如主键字段 * D:但是词条搜索,要求ID必须是字符串,可以先手动删除deleteDocuments(数值范围查询锁定ID) */ public static void editLuceneIndex() throws Exception{ //创建目录对象 Directory directory = FSDirectory.open(Paths.get(INDEX_PATH)); //创建配置对象 IndexWriterConfig config = new IndexWriterConfig(new IKAnalyzer()); //创建索引写入工具 IndexWriter writer = new IndexWriter(directory, config); //创建新的文档数据 Document document = new Document(); document.add(new StringField("id", "1", Store.YES)); document.add(new TextField("zl", "官渡区民航路26号香樟俊园三期2幢3层307室", Store.YES)); //修改索引 writer.updateDocument(new Term("id","1"), document); writer.commit(); writer.close(); }
4.索引删除数据
代码如下:
/** * 删除索引 * * 1.创建文档对象目录 * 2.创建索引写入器配置对象 * 3.创建索引写入器 * 4.删除 * 5.提交 * 6.关闭 * * 注意: * 一般,为了进行精确删除,我们会根据唯一字段来删除,比如:ID * 如果使用Term删除,要求ID也必须是字符串类型 */ public static void deleteLuceneIndex() throws Exception{ //创建文档目录对象 Directory directory = FSDirectory.open(Paths.get(INDEX_PATH)); //创建索引写入配置对象 IndexWriterConfig config = new IndexWriterConfig(new IKAnalyzer()); //创建索引写入对象 IndexWriter writer = new IndexWriter(directory, config); //根据词条进行删除 writer.deleteDocuments(new Term("id","1")); //删除所有 //writer.deleteAll(); writer.commit(); writer.close(); }
另外关于Lucene中的其他应用,比如高亮显示、排序、分页、得分算法等,这些方法在API中都是存在的,因为个人没有用到这些功能,所以此处就不一一列举了