Lucene8.8.2官方文档
一、概念
1.1 基础
数据分类
- 结构化数据:格式固定、长度固定、有一定格式、数据类型固定。例如数据库中的数据
- 非结构化数据:格式不固定、长度不固定、数据类型不固定,例如word文档、pdf文档、邮件、html、txt,格式不固定
数据的查询
- 结构化数据查询:SQL语句,Structured Query Language 结构化查询语言。查询简单、速度快
- 非结构化数据查询:从文本文件中找出包含某关键字的文字
- 法一:使用程序将文档读取到内存中,然后匹配字符串,顺序扫描
- 法二:将非结构化数据转成结构化数据再查询
- 根据空格进行字符串拆分,得到单词列表,基于单词列表创建一个索引(为了提高查询速率,创建某种数据结构的集合)
- 查询索引,根据单词和文档的对应关系,找到文档列表。
- 这个过程叫做全文检索
1.2 全文检索
全文检索:先创建索引,然后查询索引的过程。
索引一次创建可以多次使用,表现为每次查询速度都很快。
应用场景
- 搜索引擎:谷歌、百度
- 站内搜索:网站内的站内搜索
只要有搜索的地方,就可以使用全文检索技术。
1.3 Lucene
Lucene是一个基于Java开发的全文检索工具包
Lucene实现全文检索的流程
创建索引
步骤
- 获得文档
- 原始文档:要基于哪些数据来进行搜索,那么这些数据就是原始文档。比如搜索引擎的原始文档就是整个互联网的网页
- 获取数据方式:搜索引擎使用爬虫获得原始文档,站内搜索直接使用数据库中数据
- 构建文档对象:对应每个原始文档创建一个Document对象,每个Document对象中包含多个Field(域),每个Field又包含文件名称、文件路径
- 分析文档(以案例为例)
- 根据空格进行字符串的拆分,得到一个单词列表。
- 把单词统一换成小写
- 去掉标点符号
- 去除停用词:无意义的词,比如and、the
- 每个关键词都封装到一个Term对象中,Term包含两部分内容
- 关键词所在的域
- 关键词本身
- 不同的域中拆分出来的相同的关键词是不同的Term
- 创建索引
- 基于关键词创建一个索引,保存到索引库
- 索引库中
- 索引
- document对象
- 关键词和文档对应关系
倒排索引结构在已经建立好的索引的基础上, 通过关键词找文档
查询索引
步骤
- 用户查询接口,如百度的搜索框
- 把关键词封装成一个查询对象
- 执行查询
- 根据要查询的关键词到对应的域上进行搜索
- 找到关键次,根据关键词找到对应文档
- 渲染结果
二、入门
2.1 创建索引库
通过lucene查询出以上所有包含spring的文件。
搭建工程
- 创建一个Java工程
- 添加Jar
- lucene-analyzers-common.jar
- lucene-core.jar
- commons-io.jar
步骤
- 创建一个Directory对象,指定索引库保存的位置
- 基于Directory对象创建一个IndexWriter对象
- 读取磁盘上的文件,对应每个文件创建一个Document对象
- 向文档对象中添加域(文件名称、大小、路径、内容)
- 把文档对象写入索引库
- 关闭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
| public class LuceneFirst { public void createIndex() throws Exception { Directory directory = FSDirectory.open(new File("C:\\Users\\meethigher\\Desktop\\gg").toPath()); IndexWriter indexWriter = new IndexWriter(directory, new IndexWriterConfig()); File dir = new File("C:\\Users\\meethigher\\Desktop\\tempStorage"); File[] files = dir.listFiles(); for (File x : files) { String fileName = x.getName(); String filePath = x.getPath(); String fileContent = FileUtils.readFileToString(x, "utf-8"); long fileSize = FileUtils.sizeOf(x);
Field fieldName = new TextField("name", fileName, Field.Store.YES); Field fieldPath = new TextField("path", filePath, Field.Store.YES); Field fieldContent = new TextField("content", fileContent, Field.Store.YES); Field fieldSize = new TextField("size", fileSize + "", Field.Store.YES); Document document = new Document(); document.add(fieldName); document.add(fieldPath); document.add(fieldContent); document.add(fieldSize); indexWriter.addDocument(document); } indexWriter.close(); }
public static void main(String[] args) throws Exception { new LuceneFirst().createIndex(); } }
|
如图所示
2.2 查看索引库
如果想要查看索引的话,需要用到luke,也就是lucene压缩包里面的一个工具jar包。
查看索引
翻页查看文档
2.3 查询索引库
步骤
- 创建Directory对象,指定索引库的位置
- 创建一个IndexReader对象
- 创建一个IndexSearcher对象,构造方法中的参数就是IndexReader对象
- 创建一个Query对象,TermQuery,根据关键词查询
- 执行查询,得到查询结果TopDocs对象。里面包含查询结果总记录数、文档列表
- 取记录数
- 取文档列表
- 打印文档内容
- 关闭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 33
| public class LuceneFirst { public void searchIndex() throws Exception { Directory directory = FSDirectory.open(new File("C:\\Users\\meethigher\\Desktop\\gg").toPath()); DirectoryReader reader = DirectoryReader.open(directory); IndexSearcher indexSearcher = new IndexSearcher(reader); Query query =new TermQuery(new Term("content","spring")); TopDocs topDocs = indexSearcher.search(query, 10); System.out.println("查询总记录数:"+topDocs.totalHits); ScoreDoc[] scoreDocs = topDocs.scoreDocs; for (ScoreDoc scoreDoc : scoreDocs) { int id=scoreDoc.doc; Document doc = indexSearcher.doc(id);
System.out.println(doc.get("name")); } reader.close(); } public static void main(String[] args) throws Exception {
new LuceneFirst().searchIndex(); } }
|
三、分析器
3.1 标准分析器
默认使用的是标准分析器StandardAnalyzer
通过查看源码可知使用的是标准分析器
查看分析器的分析效果
使用Analyzer对象的TokenStream方法返回一个TokenStream对象,词对象中包含了最终的分词结果
实现步骤
- 创建一个Analyzer对象,StandardAnalyzer对象
- 使用分析器对象的TokenStream方法获得一个TokenStream对象
- 向TokenStream对象中设置一个引用,相当于是一个指针
- 调用TokenStream对象的reset方法,如果不调用,就会抛异常
- 使用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
| public class LuceneFirst { public void testTokenStream() throws Exception { Analyzer analyzer=new StandardAnalyzer(); TokenStream tokenStream = analyzer.tokenStream("", "The Spring Framework provides a comprehensive programming and configuration model."); CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class); tokenStream.reset(); while(tokenStream.incrementToken()){ System.out.println(charTermAttribute.toString()); } tokenStream.close(); } public static void main(String[] args) throws Exception {
new LuceneFirst().testTokenStream(); } }
|
3.2 第三方分析器
像标准分析器如果用来处理中文内容,就会按照一个汉字来分割,就不能用词语来检索了。
处理中文内容可以使用IKAnalyzer工具包
- 添加IKAnalyzer的包
- 把配置文件和扩展词典添加到工程classpath下
注意
扩展词典不能使用windows记事本编辑,保证扩展词典编码格式是UTF-8
windows中记事本的utf-8默认是utf-8+bom格式的,非标准utf-8
扩展词典可以用于添加一些新词
停用词词典:无意义词或者敏感词
引入依赖
1 2 3 4 5
| <dependency> <groupId>com.jianggujin</groupId> <artifactId>IKAnalyzer-lucene</artifactId> <version>8.0.0</version> </dependency>
|
测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class LuceneFirst { public void testIKAnalyzer() throws Exception { Analyzer analyzer=new IKAnalyzer(); TokenStream tokenStream = analyzer.tokenStream("", "向TokenStream对象中政府邸设置一个引用,相当于是一个指针"); CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class); tokenStream.reset(); while(tokenStream.incrementToken()){ System.out.println(charTermAttribute.toString()); } tokenStream.close(); } public static void main(String[] args) throws Exception {
new LuceneFirst().testIKAnalyzer(); } }
|
四、索引库维护
4.1 常用Field使用
Field域的属性
- 是否分析:是否对域内容进行分析,也就是分词。比如身份证号,根本不需要分词
- 是否索引:将分析后的词进行索引,通过索引来查询
- 是否存储:将域存储在本地
Field类 | 数据类型 | 是否分词(分析) | 是否索引 | 是否存储 |
---|
StringField | 字符串 | 否 | 是 | 自选 |
LongPoint | Long | 是 | 是 | 否 |
StoredField | 支持多种类型 | 否 | 否 | 是 |
TextField | 字符串或者流 | 是 | 是 | 自选 |
这个可以自己参照官网
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
| public class LuceneFirst { public void createIndex() throws Exception { Directory directory = FSDirectory.open(new File("C:\\Users\\meethigher\\Desktop\\gg").toPath()); IndexWriter indexWriter = new IndexWriter(directory, new IndexWriterConfig(new IKAnalyzer())); File dir = new File("C:\\Users\\meethigher\\Desktop\\tempStorage"); File[] files = dir.listFiles(); for (File x : files) { String fileName = x.getName(); String filePath = x.getPath(); String fileContent = FileUtils.readFileToString(x, "utf-8"); long fileSize = FileUtils.sizeOf(x); Field fieldName = new TextField("name", fileName, Field.Store.YES); Field fieldPath = new StoredField("path", filePath); Field fieldContent = new TextField("content", fileContent, Field.Store.YES); Field fieldSizeValue = new LongPoint("size", fileSize); Field fieldSizeStore=new StoredField("size",fileSize); Document document = new Document(); document.add(fieldName); document.add(fieldPath); document.add(fieldContent); document.add(fieldSizeValue); document.add(fieldSizeStore); indexWriter.addDocument(document); } indexWriter.close(); } public static void main(String[] args) throws Exception { new LuceneFirst().createIndex(); } }
|
4.2 添加文档
执行下面的脚本,会将文档追加到索引库里
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class IndexManager { public void addDoc() throws Exception{ IndexWriter indexWriter = new IndexWriter(FSDirectory.open(new File("C:\\Users\\meethigher\\Desktop\\gg").toPath()), new IndexWriterConfig(new IKAnalyzer())); Document doc = new Document(); doc.add(new TextField("name","周杰伦", Field.Store.YES)); doc.add(new TextField("content","你三婶摸男人?", Field.Store.NO)); doc.add(new StoredField("path","C:\\Users\\meethigher\\Desktop\\gg")); indexWriter.addDocument(doc); indexWriter.close(); }
public static void main(String[] args) throws Exception { new IndexManager().addDoc(); } }
|
虽然没有保存content,但还是能查询得到的。
4.3 删除文档
两种方式
- 删除所有
- 删除查询到的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class IndexManager { public void deleteIndex() throws Exception{ IndexWriter indexWriter = new IndexWriter(FSDirectory.open(new File("C:\\Users\\meethigher\\Desktop\\gg").toPath()), new IndexWriterConfig(new IKAnalyzer())); indexWriter.deleteAll(); indexWriter.close(); } public void deleteDocumentByQuery() throws Exception { IndexWriter indexWriter = new IndexWriter(FSDirectory.open(new File("C:\\Users\\meethigher\\Desktop\\gg").toPath()), new IndexWriterConfig(new IKAnalyzer())); indexWriter.deleteDocuments(new TermQuery(new Term("content","三婶"))); indexWriter.close(); } public static void main(String[] args) throws Exception { new IndexManager().deleteDocumentByQuery(); } }
|
4.4 更新文档
先查询再更新
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class IndexManager { public void updateDoc() throws Exception { IndexWriter indexWriter = new IndexWriter(FSDirectory.open(new File("C:\\Users\\meethigher\\Desktop\\gg").toPath()), new IndexWriterConfig(new IKAnalyzer())); Document doc = new Document(); doc.add(new TextField("name","更新", Field.Store.YES)); doc.add(new TextField("content","更新后内容",Field.Store.YES)); indexWriter.updateDocument(new Term("name","周杰伦"),doc); indexWriter.close(); } public static void main(String[] args) throws Exception {
new IndexManager().updateDoc(); } }
|
五、索引库的查询
查询方式
- 使用Query的子类
- TermQuery:根据关键词进行查询。需要指定要查询的域以及要查询的关键词
- RangeQuery:范围查询
- 使用QueryParser:可以对要查询的内容,先进行分词,然后基于分词结果来进行查询。类似于搜索引擎的搜索
范围查询
比较数据用的LongPoint,读取数据是StoredField,即便没有StoredField也是可以查询出来的,只不过size的大小是null。参照4.1
未存储到索引库也是可以查询的,参照4.2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class SearchIndex { public void rangeQuery() throws Exception { Directory directory = FSDirectory.open(new File("C:\\Users\\meethigher\\Desktop\\gg").toPath()); DirectoryReader reader = DirectoryReader.open(directory); IndexSearcher indexSearcher = new IndexSearcher(reader); Query query = LongPoint.newRangeQuery("size", 0L, 100L); TopDocs search = indexSearcher.search(query, 10); System.out.println("总条数:" + search.totalHits); ScoreDoc[] scoreDocs = search.scoreDocs; for (ScoreDoc sd : scoreDocs) { int doc = sd.doc; Document document = indexSearcher.doc(doc); System.out.println(document.get("name")); System.out.println(document.get("content")); System.out.println(document.get("size")); } } public static void main(String[] args) throws Exception { new SearchIndex().rangeQuery(); } }
|
QueryParser查询
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class SearchIndex { public void queryParser() throws Exception { Directory directory = FSDirectory.open(new File("C:\\Users\\meethigher\\Desktop\\gg").toPath()); DirectoryReader reader = DirectoryReader.open(directory); IndexSearcher indexSearcher = new IndexSearcher(reader); QueryParser queryParser = new QueryParser("name", new IKAnalyzer()); Query query = queryParser.parse("周杰"); TopDocs search = indexSearcher.search(query, 10); System.out.println("总条数:" + search.totalHits); ScoreDoc[] scoreDocs = search.scoreDocs; for (ScoreDoc sd : scoreDocs) { int doc = sd.doc; Document document = indexSearcher.doc(doc); System.out.println(document.get("name")); System.out.println(document.get("content")); System.out.println(document.get("size")); } } public static void main(String[] args) throws Exception { new SearchIndex().queryParser(); } }
|
最后放上最终的pom
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
| <?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>top.meethigher</groupId> <artifactId>lucene-notes</artifactId> <version>1.0-SNAPSHOT</version>
<dependencies> <dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-core</artifactId> <version>8.8.2</version> </dependency> <dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-analyzers-common</artifactId> <version>8.8.2</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.8.0</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13</version> <scope>test</scope> </dependency> <dependency> <groupId>com.jianggujin</groupId> <artifactId>IKAnalyzer-lucene</artifactId> <version>8.0.0</version> </dependency> <dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-queryparser</artifactId> <version>8.8.2</version> </dependency> </dependencies> </project>
|