Lucene入门

it2023-10-29  74

Lucene

文章目录

Lucene一、 全文检索二、 Lucene 实现全文检索的流程2.1 创建索引2.2 查询索引 三、 入门案例(简单使用 Lucene)3.1 导入依赖 jar 包3.2 建立索引库3.3 查询索引库四、 分析器五、 索引库维护六、索引库查询

一、 全文检索

数据的分类和查询方法

数据的分类

结构化数据

例如数据库中的数据。

格式固定、长度固定、数据类型固定。

非结构化数据

word 文档、pdf 文档、邮件、html、txt

格式不固定、长度不固定、数据类型不固定。

数据的查询

结构化数据的查询

SQL 语句,查询结构化数据的方法。简单、速度快。

非结构化数据的查询

例:从文本文件中找出包含 Spring 单词的文件:

目测法。

使用程序把文档读取到内存中,然后匹配字符串,顺序扫描。

把非结构化数据变成结构化数据:

先根据空格进行字符串拆分,得到一个单词列表,基于单词列表创建一个索引,然后查询索引,根据单词和文档的对应关系找到文档列表。这个过程就叫做全文检索。

全文检索定义

先创建索引然后查询索引的过程就叫做全文检索。索引一次创建可以多次使用,表现为每次查询速度很快。

全文检索应用场景

搜索引擎

百度、360搜索、谷歌、搜狗。

站内搜索

论坛内搜索、微博搜索、文章搜索、淘宝搜索、京东搜索。

只要是有搜索的地方就可以使用全文检索技术。

Lucene

Lucene 是一个基于 Java 开发全文检索工具包。

二、 Lucene 实现全文检索的流程

索引和搜索流程图

2.1 创建索引

获得文档

原始文档:要基于哪些数据来进行搜索,那么这些数据就是原始文档。

搜索引擎:使用爬虫获取原始文档。

站内搜索:数据库中的数据。

构建文档对象

对应每个原始文档创建一个 Document 对象,每个 Document 对象中包含多个域 (field),域中保存的就是原始文档数据。域的名称和域的值构成一对键值对。

每个文档都有一个唯一的编号,也就是文档的 id。

分析文档

就是分词过程

根据空格进行字符串拆分,得到一个单词列表。

把单词统一转换成小(或大)写。

去除标点符号。

去除停用词(也就是无意义的词)。

每个关键字都封装成一个 Term 对象。Term 对象中包含两部分内容:一是关键字所在的域、二是关键词本身,不同的域中拆分出来的相同的关键词是不同的 Term。

创建索引

基于关键词列表创建一个索引,保存到索引库中。

索引库中包含:

索引

Document 对象

关键词和文档的对应关系

通过词语找文档,这种索引的结构叫做倒排索引结构。

2.2 查询索引

用户查询接口

用户输入查询条件的地方。例如:百度的搜索框。

把关键词封装成一个查询对象

查询对象包含:要查询的域,要搜索的关键字。

执行查询

根据要查询的关键词到对应的域上进行搜索。找到关键词,根据关键词找到对应的文档。

渲染结果

根据文档的 id 找到文档的对象,对关键词进行高亮显示、分页处理。

三、 入门案例(简单使用 Lucene)

3.1 导入依赖 jar 包

* lucene-analyzer-commom-7.4.0.jar * lucene-core-7.4.0.jar * commons-io.jar

3.2 建立索引库

创建一个 Directory 对象;

基于 Directory 对象创建一个 IndexWriter 对象;

读取磁盘上的文件,对应每一个文件创建一个文档对象;

向文档对象中添加域;

把文档对象写入索引库;

关闭 IndexWriter 对象。

/** * 1. 建立索引库,并把文档映射到索引 * */ @Test public void buildIndexStore() throws IOException { // 1. 创建一个 Directory 对象,指定索引库的位置 Directory directory = FSDirectory.open(new File("E:\\java\\15_Lucene_IndexStore").toPath()); // 2. 创建 写索引 对象 IndexWriterConfig config = new IndexWriterConfig(); IndexWriter indexWriter = new IndexWriter(directory, config); // 3. 读取磁盘上的文件,对应每个文件创建一个文档对象。 File file = new File("E:\\java\\15_Lucene\\01_lucene_demo\\searchsource"); File[] files = file.listFiles(); for (File f: files) { // 获取文件名 String fileName = f.getName(); System.out.println(fileName); // 获取文件路径 String filePath = f.getPath(); // 获取文件内容 String fileContent = FileUtils.readFileToString(f, "utf-8"); // 获取文件大小 long fileSize = FileUtils.sizeOf(f); // 参数1:域的名称,参数2:域的内容,参数3:是否存储 Field fieldName = new TextField("fileName", fileName, Field.Store.YES); Field fieldPath = new TextField("filePath", filePath, Field.Store.YES); Field fieldContent = new TextField("fileContent", fileContent, Field.Store.YES); Field fieldSize = new TextField("fileSize", String.valueOf(fileSize), Field.Store.YES); // 4. 创建文档对象 Document doc = new Document(); // 向文档对象中添加索引库 doc.add(fieldName); doc.add(fieldPath); doc.add(fieldContent); doc.add(fieldSize); // 5. 把文档对象写入索引库 indexWriter.addDocument(doc); } // 6. 关闭 IndexWriter 对象 indexWriter.close(); }

注:可以使用 luke 查看索引库中的内容。

3.3 查询索引库

创建一个 Directory 对象,指定索引库的位置;创建一个 IndexReader 对象;创建一个 IndexSearcher 对象,构造方法中的参数为 IndexReader 对象;创建一个 Query 对象,TermQuery;执行查询,得到一个 TopDocs 对象;取查询结果的总记录数;取文档列表;打印文档中的内容;关闭 IndexReader 对象。 /** * 2. 从索引库中查找文档 * */ @Test public void searchIndex() throws IOException { // 1. 创建一个 Directory 对象,指定索引库的位置 Directory directory = FSDirectory.open(new File("E:\\java\\15_Lucene_IndexStore").toPath()); // 2. 创建一个 IndexReader 对象 IndexReader indexReader = DirectoryReader.open(directory); // 3. 创建一个 IndexSearcher 对象 IndexSearcher indexSearcher = new IndexSearcher(indexReader); // 4. 创建一个 Query 对象,TermQuery Query query = new TermQuery(new Term("fileContent", "spring")); // 5. 执行查询,得到一个TopDocs对象 // 传入查询条件并设置最多返回 10 条结果 TopDocs topDocs = indexSearcher.search(query, 10); System.out.println("总的记录条数为:" + topDocs.totalHits); // 6. 获取文档列表 ScoreDoc[] docs = topDocs.scoreDocs; // 7. 打印文档中的内容 for (ScoreDoc d : docs ) { // 获取文档的 id int doc = d.doc; Document document = indexSearcher.doc(doc); System.out.println(document.get("fileName")); System.out.println(document.get("filePath")); System.out.println(document.get("fileSize")); System.out.println("-------------------------------"); } // 8. 关闭 indexReader 对象 indexReader.close(); }

四、 分析器

默认情况下使用的是标准分析器 StandardAnalyzer,但是 StandardAnalyzer 对中文分词的支持不是很友好。为了实现对中文的友好支持,可以使用 IKAnalyzer 类。

查看标准分析器 StandardAnalyzer 的分析效果

使用 Analyzer 对象的 tokenStream 方法返回一个 tokenStream 对象。此对象中包含了最终的分词结果。

实现步骤:

创建一个 Analyzer 对象,StandardAnalyzer 对象;使用分析器对象的 tokenStream 方法获得一个 TokenStream 对象;向 TokenStream 对象中设置一个引用,相当于是一个指针;调用 TokenStream 对象的 reset 方法,如果不调用则会抛出异常;使用 while 循环遍历 TokenStream 对象;关闭 TokenStream 对象。

代码演示:

/** * 3. 测试使用 Analyzer * */ @Test public void testIKAnalyzer() throws IOException { // 1. 创建一个Analyzer对象,StandardAnalyzer 对象,主要是对英文进行分析 // Analyzer analyzer = new StandardAnalyzer(); // IKAnalyzer 主要是对中文进行分析 IKAnalyzer analyzer = new IKAnalyzer(); // 2. 使用分析器对象的 tokenStream 方法获得一个 TokenStream 对象 // TokenStream tokenStream = analyzer.tokenStream("", "Learn how to create a web page with Spring MVC."); TokenStream tokenStream = analyzer.tokenStream("","今天是个好日子啊,真的是个好日子啊,白日依山尽"); // 3. 向 TokenStream 对象中设置一个引用,相当于是一个指针 CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class); // 4. 调用 TokenStream 对象的 reset 方法。如果不调用抛异常 tokenStream.reset(); // 5. 使用while循环遍历TokenStream对象 while(tokenStream.incrementToken()){ System.out.println(charTermAttribute.toString()); } // 6. 关闭 tokenStream 对象 tokenStream.close(); }

IKAnalyzer 的使用方法

把 IKAnalyzer 的 jar 包添加到工程中;

* IK-Analyzer-1.0-SNAPSHOT.jar

把配置文件和扩展词典添加到工程的 classpath 下。

* hotword.dic * IKAnalyzer.cfg.xml * stopword.dic

注意:扩展词严禁使用 windows 自带的记事本编辑,主要是为了保证编码格式为 “UTF-8”。

扩展词典:添加一些新词;停用词词典:无意义的词或者是敏感词汇。

代码演示:

// 创建 写索引 对象时,把 IKAnalyzer 对象添加进去配置对象即可 IndexWriterConfig config = new IndexWriterConfig(new IKAnalyzer());

五、 索引库维护

添加文档

/** * 1. 添加文档 * */ @Test public void addDocument() throws IOException { Document document = new Document(); document.add(new TextField("name", "文章标题", Field.Store.YES)); // 是否存储的标准:是否要将内容展示给用户 document.add(new TextField("content", "这是文章的内容哦,弟中弟", Field.Store.NO)); // 不分析,不索引,但要Field存储在文档中 document.add(new StoredField("path", "c:/temp/hello")); // 把 document 对象存储到索引域中 indexWriter.addDocument(document); indexWriter.close(); }

删除文档

删除全部文档

/** * 2. 删除所有文档 * */ @Test public void deleteAllDocuments() throws IOException { indexWriter.deleteAll(); indexWriter.close(); }

根据查询、关键词查询文档

/** * 3. 删除指定文档 * */ @Test public void deleteDocumentsByTerm() throws Exception{ // 删除域名为 name 域中 包含 “文章” 关键字的文档 indexWriter.deleteDocuments(new Term("name", "文章")); indexWriter.close(); }

更新文档

/** * 4. 更新文档 * */ @Test public void updateDocument() throws Exception{ Document document = new Document(); document.add(new TextField("name", "更新后,这是第一个标题", Field.Store.YES)); document.add(new TextField("name1", "更新后,哈哈,介系第二个嘛", Field.Store.YES)); document.add(new TextField("name2", "这是更新后的第三个标题", Field.Store.YES)); indexWriter.updateDocument(new Term("name", "标题"), document); indexWriter.close(); }

六、索引库查询

使用 Query 的子类

TermQuery

根据关键词进行查询,需要指定要查询的域以及关键词。

public void testTermQuery() throws IOException { // 1. 创建一个 Directory 对象,指定索引库的位置 Directory directory = FSDirectory.open(new File("E:\\java\\15_Lucene_IndexStore").toPath()); // 2. 创建一个 IndexReader 对象 IndexReader indexReader = DirectoryReader.open(directory); // 3. 创建一个 IndexSearcher 对象 IndexSearcher indexSearcher = new IndexSearcher(indexReader); // 4. 创建一个 Query 对象,TermQuery Query query = new TermQuery(new Term("fileContent", "spring")); // 5. 执行查询,得到一个TopDocs对象 // 传入查询条件并设置最多返回 10 条结果 TopDocs topDocs = indexSearcher.search(query, 10); System.out.println("总的记录条数为:" + topDocs.totalHits); // 6. 获取文档列表 ScoreDoc[] docs = topDocs.scoreDocs; // 7. 打印文档中的内容 for (ScoreDoc d : docs ) { // 获取文档的 id int doc = d.doc; Document document = indexSearcher.doc(doc); System.out.println(document.get("fileName")); System.out.println(document.get("filePath")); System.out.println(document.get("fileSize")); System.out.println("-------------------------------"); } // 8. 关闭 indexReader 对象 indexReader.close(); }

RangeQuery

范围查询。

代码演示:

/** * 限定文件大小范围查找 * */ @Test public void testRangeQuery() throws Exception{ // 查找条件为:域名为 size,且文件大小在 1~1000 字节之间 Query query = LongPoint.newRangeQuery("fileSize", 1, 1000); // 自己写的查询方法,具体怎么写,自己去上面查琢磨琢磨 printQuery(query); }

使用 QueryParser 进行查询

可以先对要查询的内容先分词,然后基于分词的结果进行查询。

需要添加如下 jar 包:

* lucene-queryparser-7.4.0.jar

代码演示:

/** * 对搜索的内容进行分析,然后查找 * */ @Test public void testQueryParser () throws Exception{ // 创建 queryParser 对象:要搜索哪个域,要对搜索的词使用什么分析器 QueryParser queryParser = new QueryParser("fileName", new IKAnalyzer()); // 要搜索的内容,并且对内容进行分析 Query query = queryParser.parse("lucene是一个Java开发的全文检索工具包"); // 执行查询 printQuery(query); }
最新回复(0)