什么是全文检索
在我们生活中的数据可分为结构化数据和非结构化数据
结构化数据
格式固定、长度固定、数据类型固定。
如数据库中的数据,
查询方法:SQL语句。
特点:简单、速度快。
非结构化数据
格式不固定、长度不固定、数据类型不固定。
如word文档、txt、图片、XML、HTML、pdf文档、邮件、图像和音频/视频信息等
查询方法:
(1)顺序扫描法(Serial Scanning)
所谓顺序扫描,比如要找内容包含某一个字符串的文件,就是一个文档一个文档的找,对于每一个文档,从头找到尾,如果此文档包含此字符串,则此文档为我们要找的文件,接着找下一个文件,直到扫描完所有的文件。如利用windows的搜索也可以搜索文件内容,只是相当的慢。
(2)全文检索(Full-text Search)
将非结构化数据中的一部分信息提取出来,重新组织,使其变得有一定结构,然后对此有一定结构的数据进行搜索,从而达到搜索相对较快的目的。这部分从非结构化数据中提取出的然后重新组织的信息,我们称之索引。
这种先建立索引,再对索引进行搜索的过程就叫全文检索(Full-text Search)。
虽然创建索引的过程也是非常耗时的,但是索引一旦创建就可以多次使用,全文检索主要处理的是查询,所以耗时间创建索引是值得的。
全文检索大体分两个过程,索引创建(Indexing)和搜索索引(Search)。
索引创建:提取结构化和非结构化数据信息,创建索引。
搜索索引:根据查询请求搜索创建的索引,然后返回查询结果。
什么是Lucene
Lucene是apache下的一个开放源代码的全文检索引擎工具包。提供了完整的查询引擎和索引引擎,部分文本分析引擎。Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能。
Lucene实现全文检索的流程
创建索引
获得文档
原始文档是指要索引和搜索的内容。原始内容包括互联网上的网页、数据库中的数据、磁盘上的文件等。
构建文档对象
获取原始内容的目的是为了索引,在索引前需要将原始内容创建成文档(Document),文档中包括一个一个的域(Field),域中存储内容。每个Document可以有多个Field,不同的Document可以有不同的Field,同一个Document可以有相同的Field(域名和域值都相同)
分析文档
将原始内容创建为包含域(Field)的文档(document),需要再对域中的内容进行分析,分析的过程是经过对原始文档提取单词、将字母转为小写、去除标点符号、去除停用词等过程生成最终的语汇单元,可以将语汇单元理解为一个一个的单词。每个单词叫做一个Term,不同的域中拆分出来的相同的单词是不同的term。term中包含两部分一部分是文档的域名,另一部分是单词的内容。
创建索引
对所有文档分析得出的语汇单元进行索引,索引的目的是为了搜索,最终要实现只搜索被索引的语汇单元从而找到Document(文档)。
创建索引是对语汇单元索引,通过词语找文档,这种索引的结构叫倒排索引结构。
倒排索引结构是根据内容(词语)找文档,如下图:
倒排索引结构也叫反向索引结构,包括索引和文档两部分,索引即词汇表,它的规模较小,而文档集合较大。
查询索引
查询索引也是搜索的过程。搜索就是用户输入关键字,从索引(index)中进行搜索的过程。根据关键字搜索索引,根据索引找到对应的文档,从而找到要搜索的内容。
创建查询
用户输入查询关键字执行搜索之前需要先构建一个查询对象,查询对象中可以指定查询要搜索的Field文档域、查询关键字等,查询对象会生成具体的查询语法
执行查询
根据查询语法在倒排索引词典表中分别找出对应搜索词的索引,从而找到索引所链接的文档链表。
案例
案例说明
对于磁盘上某一个目录下的所有文本文件进行全文检索,通过关键字搜索文件,凡是文件名或文件内容包括关键字的文件都需要找出来。还可以根据中文词语进行查询。
环境搭建
创建maven工程,导入相关jar包坐标
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<version>7.4.0</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-common</artifactId>
<version>7.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-queryparser</artifactId>
<version>7.4.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.jianggujin/IKAnalyzer-lucene -->
<dependency>
<groupId>com.jianggujin</groupId>
<artifactId>IKAnalyzer-lucene</artifactId>
<version>8.0.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
resources目录下创建IKAnalyzer.cfg.xml
(名字固定)
可以配置用户自定义扩展词典和停用词典
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!--用户可以在这里配置自己的扩展字典 -->
<entry key="ext_dict">hotword.dic;</entry>
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords">stopword.dic;</entry>
</properties>
同时在此目录下创建hotword.dic和stopword.dic文件,分别存放扩展词和停用词
创建索引
public void CreateIndex() throws Exception {
//创建一个Director对象,指定索引库保存在磁盘上
Directory directory = FSDirectory.open(new File("此处填写索引库保存的目录名").toPath());
// 配置IndexWriter使用中文分析器
IndexWriterConfig config = new IndexWriterConfig(new IKAnalyzer());
//创建一个IndexWriter对象
IndexWriter indexWriter = new IndexWriter(directory, config);
//读取磁盘上的文件,对应每个文件创建一个文档对象。
File dir = new File("此处填写需要全文检索的文件所在目录名");
File[] files = dir.listFiles();
for (File f :files)
{
//文件名
String fileName = f.getName();
//文件的路径
String filePath = f.getPath();
//文件的内容
String fileContent = FileUtils.readFileToString(f, "utf-8");
//文件的大小
long fileSize = FileUtils.sizeOf(f);
//创建Field(域对象)
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对象
indexWriter.close();
}
查询索引
public void SearchIndex() throws Exception {
//创建一个Director对象,指定索引库的位置
Directory directory = FSDirectory.open(new File("此处填写你的索引库位置").toPath());
//创建一个IndexReader对象
IndexReader indexReader = DirectoryReader.open(directory);
//创建一个IndexSearcher对象
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
//创建一个Query对象TermQuery,指定查询的域和查询的关键词。
//Term 参数一:想在查找的域 如在content里查找
//Term 参数二:想要匹配的内容 如"java"
Query query = new TermQuery(new Term("", ""));
//执行查询,得到一个TopDocs对象
//query:查询对象
//10:查询结果返回的最大记录数
TopDocs topDocs = indexSearcher.search(query, 10);
//取查询结果的总记录数
System.out.println("查询总记录数:" + topDocs.totalHits);
//取文档列表
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
//打印文档中的内容
for (ScoreDoc doc: scoreDocs)
{
//拿到文档id
int docId = doc.doc;
//根据id取文档对象
Document document = indexSearcher.doc(docId);
System.out.println(document.get("name"));
System.out.println(document.get("path"));
System.out.println(document.get("size"));
System.out.println(document.get("content"));
System.out.println("-----------------");
}
//关闭IndexReader对象
indexReader.close();
}
数值范围查询
public void RangeQuery() throws Exception {
//创建一个IndexReader对象
IndexReader indexReader = DirectoryReader.open(FSDirectory.open(new File("此处填写你的索引库位置").toPath()));
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
//创建一个Query对象 查询文件大小为1字节到2048字节的文件
Query query = LongPoint.newRangeQuery("size", 1, 2048);
//执行查询
TopDocs topDocs = indexSearcher.search(query, 10);
System.out.println("总记录数:" + topDocs.totalHits);
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
for (ScoreDoc doc:scoreDocs){
//取文档id
int docId = doc.doc;
//根据id取文档对象
Document document = indexSearcher.doc(docId);
System.out.println(document.get("name"));
System.out.println(document.get("path"));
System.out.println(document.get("size"));
System.out.println(document.get("content"));
System.out.println("-----------------");
}
indexReader.close();
}
queryparser查询
通过QueryParser也可以创建Query,QueryParser提供一个Parse方法,此方法可以直接根据查询语法来查询。此方法会将输入语句解析为多个词项,根据这些词项全文检索
queryParser创建时需要使用到分析器。建议创建索引时使用的分析器和查询索引时使用的分析器要一致
public void testQueryParser() throws Exception {
//创建一个IndexReader对象
IndexReader indexReader = DirectoryReader.open(FSDirectory.open(new File("此处填写你的索引库位置").toPath()));
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
//创建一个QueryPaser对象,两个参数
//参数1:默认搜索域,参数2:分析器对象
QueryParser queryParser = new QueryParser("name", new IKAnalyzer());
//使用QueryPaser对象创建一个Query对象
Query query = queryParser.parse("输入想要检索的语句");
System.out.println(query);
//执行查询
TopDocs topDocs = indexSearcher.search(query, 10);
System.out.println("总记录数:" + topDocs.totalHits);
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
for (ScoreDoc doc:scoreDocs){
//取文档id
int docId = doc.doc;
//根据id取文档对象
Document document = indexSearcher.doc(docId);
System.out.println(document.get("name"));
System.out.println(document.get("path"));
System.out.println(document.get("size"));
System.out.println(document.get("content"));
System.out.println("-----------------");
}
indexReader.close();
}
索引库的维护
索引库的添加
public void addDocument() throws Exception {
//创建一个IndexWriter对象,需要使用IKAnalyzer作为分析器
IndexWriter indexWriter = new IndexWriter(FSDirectory.open(new File("此处填写索引库保存的目录名").toPath()), new IndexWriterConfig(new IKAnalyzer()));
//创建一个Document对象
Document document = new Document();
//向document对象中添加域
document.add(new TextField("name", "新增文件名", Field.Store.YES));
document.add(new TextField("content", "新增内容", Field.Store.NO));
document.add(new StoredField("path", "此处填写新文件路径"));
document.add(new LongPoint("size",1024));
document.add(new StoredField("size",1024));
// 把文档写入索引库
indexWriter.addDocument(document);
//关闭索引库
indexWriter.close();
}
索引库删除
删除全部
public void deleteAllDocument() throws Exception {
//创建一个IndexWriter对象,需要使用IKAnalyzer作为分析器
IndexWriter indexWriter = new IndexWriter(FSDirectory.open(new File("此处填写索引库保存的目录名").toPath()), new IndexWriterConfig(new IKAnalyzer()));
//删除全部文档
indexWriter.deleteAll();
//关闭索引库
indexWriter.close();
}
指定查询条件删除
public void deleteDocumentByQuery() throws Exception {
//创建一个IndexWriter对象,需要使用IKAnalyzer作为分析器
IndexWriter indexWriter = new IndexWriter(FSDirectory.open(new File("此处填写索引库保存的目录名").toPath()), new IndexWriterConfig(new IKAnalyzer()));
indexWriter.deleteDocuments(new Term("查询域名", "查询关键词"));
indexWriter.close();
}
索引库修改
先删除后添加
public void updateDocument() throws Exception {
//创建一个IndexWriter对象,需要使用IKAnalyzer作为分析器
IndexWriter indexWriter = new IndexWriter(FSDirectory.open(new File("此处填写索引库保存的目录名").toPath()), new IndexWriterConfig(new IKAnalyzer()));
//创建一个新的文档对象
Document document = new Document();
//向文档对象中添加域
document.add(new TextField("name", "更新文档", Field.Store.YES));
//更新操作
indexWriter.updateDocument(new Term("查询域名", "查询关键词"), document);
//关闭索引库
indexWriter.close();
}
今天的分享就到这里了,希望大家能够有所收获,欢迎关注。