一、什么是全文检索
1、数据分类
我们生活中的数据总体分为两种:结构化数据和非结构化数据。
- 结构化数据:指具有固定格式或有限长度的数据,如数据库,元数据等。
- 非结构化数据:指不定长或无固定格式的数据,如邮件,word文档等磁盘上的文件
2、结构化数据搜索
常见的结构化数据也就是数据库中的数据。在数据库中搜索很容易实现,通常都是使用sql语句进行查询,而且能很快的得到查询结果。
为什么数据库搜索很容易?
因为数据库中的数据存储是有规律的,有行有列而且数据格式、数据长度都是固定的。
3、非结构化数据查询方法
(1)顺序扫描法(Serial Scanning)
所谓顺序扫描,比如要找内容包含某一个字符串的文件,就是一个文档一个文档的看,对于每一个文档,从头看到尾,如果此文档包含此字符串,则此文档为我们要找的文件,接着看下一个文件,直到扫描完所有的文件。如利用windows的搜索也可以搜索文件内容,只是相当的慢。
(2)全文检索(Full-text Search)
将非结构化数据中的一部分信息提取出来,重新组织,使其变得有一定结构,然后对此有一定结构的数据进行搜索,从而达到搜索相对较快的目的。这部分从非结构化数据中提取出的然后重新组织的信息,我们称之索引。
例如:字典。字典的拼音表和部首检字表就相当于字典的索引,对每一个字的解释是非结构化的,如果字典没有音节表和部首检字表,在茫茫辞海中找一个字只能顺序扫描。然而字的某些信息可以提取出来进行结构化处理,比如读音,就比较结构化,分声母和韵母,分别只有几种可以一一列举,于是将读音拿出来按一定的顺序排列,每一项读音都指向此字的详细解释的页数。我们搜索时按结构化的拼音搜到读音,然后按其指向的页数,便可找到我们的非结构化数据——也即对字的解释。
这种先建立索引,再对索引进行搜索的过程就叫全文检索(Full-text Search)。
虽然创建索引的过程也是非常耗时的,但是索引一旦创建就可以多次使用,全文检索主要处理的是查询,所以耗时间创建索引是值得的。
二、如何实现全文检索
可以使用Lucene实现全文检索。Lucene是apache下的一个开放源代码的全文检索引擎工具包。提供了完整的查询引擎和索引引擎,部分文本分析引擎。Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能。
三、全文检索的应用场景
对于数据量大、数据结构不固定的数据可采用全文检索方式搜索,比如百度、Google等搜索引擎、论坛站内搜索、电商网站站内搜索等。
以上内容都是参考黑马的笔记文档,具体内容来源如下
具体可参考:https://www.suibibk.com/fileupload/files/202010/lucene.docx
四、全文检索代码实现
文档中用的是普通的java工程,需要自己引入jar包,我这里就是maven工程,pom.xml引入依赖如下:
<!-- https://mvnrepository.com/artifact/junit/junit --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13</version></dependency><!-- lucene --><!-- https://mvnrepository.com/artifact/org.apache.lucene/lucene-core --><dependency><groupId>org.apache.lucene</groupId><artifactId>lucene-core</artifactId><version>7.4.0</version></dependency><!-- https://mvnrepository.com/artifact/commons-io/commons-io --><dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.6</version></dependency><dependency><groupId>org.wltea</groupId><artifactId>IKAnalyzer</artifactId><version>1.0</version></dependency><!-- https://mvnrepository.com/artifact/org.apache.lucene/lucene-queryparser --><dependency><groupId>org.apache.lucene</groupId><artifactId>lucene-queryparser</artifactId><version>7.4.0</version></dependency>
上面IKAnalyzer-1.0.jar可能maven仓库中不存在,并且配置文件也没有,这里提供下下载链接:https://www.suibibk.com/fileupload/files/202010/IKAnalyzer.zip 有需要的可以直接下载。
1、创建索引
CreateIndex.java
package com.suibibk.lucene;import java.io.File;import org.apache.commons.io.FileUtils;import org.apache.lucene.analysis.Analyzer;import org.apache.lucene.document.Document;import org.apache.lucene.document.Field;import org.apache.lucene.document.TextField;import org.apache.lucene.index.IndexWriter;import org.apache.lucene.index.IndexWriterConfig;import org.apache.lucene.store.Directory;import org.apache.lucene.store.FSDirectory;import org.wltea.analyzer.lucene.IKAnalyzer;/*** 创建索引* @author pc**/public class CreateIndex {public static void main(String[] args) throws Exception {Long start = System.currentTimeMillis();//第一步:创建一个java工程,并导入jar包。//第二步:创建一个indexwriter对象。//1)指定索引库的存放位置Directory对象//指定索引库存放的路径//D:\temp\indexDirectory directory = FSDirectory.open(new File("F:\\Source\\Index").toPath());//索引库还可以存放到内存中//Directory directory = new RAMDirectory();//2)指定一个IndexWriterConfig对象。//创建indexwriterCofig对象// IndexWriterConfig config = new IndexWriterConfig();//加入中文分词Analyzer analyzer = new IKAnalyzer();IndexWriterConfig config = new IndexWriterConfig(analyzer);//创建indexwriter对象IndexWriter indexWriter = new IndexWriter(directory, config);//原始文档的路径File dir = new File("E:\\data\\table\\contents");for (File f : dir.listFiles()) {//文件名String fileName = f.getName();//文件内容String fileContent = FileUtils.readFileToString(f, "UTF-8");//文件路径String filePath = f.getPath();//文件的大小long fileSize = FileUtils.sizeOf(f);//第三步:创建field对象//第一个参数:域的名称//第二个参数:域的内容//第三个参数:是否存储Field fileNameField = new TextField("filename", fileName, Field.Store.NO);//文件内容域Field fileContentField = new TextField("content", fileContent, Field.Store.NO);//文件路径域(不分析、不索引、只存储)Field filePathField = new TextField("path", filePath, Field.Store.YES);//文件大小域Field fileSizeField = new TextField("size", fileSize + "", Field.Store.NO);//第四步:创建document对象,将field添加到document对象中。Document document = new Document();document.add(fileNameField);document.add(fileContentField);document.add(filePathField);document.add(fileSizeField);//第四步:使用indexwriter对象将document对象写入索引库,此过程进行索引创建。并将索引和document对象写入索引库。indexWriter.addDocument(document);}//第五步:关闭IndexWriter对象。indexWriter.close();Long end = System.currentTimeMillis();System.out.println("创建索引耗时:"+(end-start)+"毫秒");}}
2、查询索引
SearchIndex.java
package com.suibibk.lucene;import java.io.File;import org.apache.lucene.analysis.Analyzer;import org.apache.lucene.analysis.TokenStream;import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;import org.apache.lucene.document.Document;import org.apache.lucene.index.DirectoryReader;import org.apache.lucene.index.IndexReader;import org.apache.lucene.index.Term;import org.apache.lucene.queryparser.classic.QueryParser;import org.apache.lucene.search.IndexSearcher;import org.apache.lucene.search.Query;import org.apache.lucene.search.ScoreDoc;import org.apache.lucene.search.TermQuery;import org.apache.lucene.search.TopDocs;import org.apache.lucene.store.Directory;import org.apache.lucene.store.FSDirectory;import org.wltea.analyzer.lucene.IKAnalyzer;/*** 查询索引* @author pc**/public class SearchIndex {public static void search() throws Exception {//指定索引库存放的路径//D:\temp\indexDirectory directory = FSDirectory.open(new File("F:\\Source\\Index").toPath());//创建indexReader对象IndexReader indexReader = DirectoryReader.open(directory);//创建indexsearcher对象IndexSearcher indexSearcher = new IndexSearcher(indexReader);//创建查询Query query = new TermQuery(new Term("content", "CentOS7下载"));//执行查询//第一个参数是查询对象,第二个参数是查询结果返回的最大值TopDocs topDocs = indexSearcher.search(query, 10);//查询结果的总条数System.out.println("查询结果的总条数:"+ topDocs.totalHits);//遍历查询结果//topDocs.scoreDocs存储了document对象的idfor (ScoreDoc scoreDoc : topDocs.scoreDocs) {//scoreDoc.doc属性就是document对象的id//根据document的id找到document对象Document document = indexSearcher.doc(scoreDoc.doc);System.out.println(document.get("filename"));//System.out.println(document.get("content"));System.out.println(document.get("path"));System.out.println(document.get("size"));System.out.println("-------------------------");}//关闭indexreader对象indexReader.close();}public static void queryParser() throws Exception {//指定索引库存放的路径//D:\temp\indexDirectory directory = FSDirectory.open(new File("F:\\Source\\Index").toPath());//创建indexReader对象IndexReader indexReader = DirectoryReader.open(directory);//创建indexsearcher对象IndexSearcher indexSearcher = new IndexSearcher(indexReader);QueryParser queryParser = new QueryParser("content", new IKAnalyzer());Query query = queryParser.parse("Lucene是java开发的");//执行查询//第一个参数是查询对象,第二个参数是查询结果返回的最大值TopDocs topDocs = indexSearcher.search(query, 10);//查询结果的总条数System.out.println("查询结果的总条数:"+ topDocs.totalHits);//遍历查询结果//topDocs.scoreDocs存储了document对象的idfor (ScoreDoc scoreDoc : topDocs.scoreDocs) {//scoreDoc.doc属性就是document对象的id//根据document的id找到document对象Document document = indexSearcher.doc(scoreDoc.doc);System.out.println(document.get("filename"));//System.out.println(document.get("content"));System.out.println(document.get("path"));System.out.println(document.get("size"));System.out.println("-------------------------");}//关闭indexreader对象indexReader.close();}public static void testTokenStream(String content) throws Exception {//创建一个标准分析器对象// Analyzer analyzer = new StandardAnalyzer();Analyzer analyzer = new IKAnalyzer();//获得tokenStream对象//第一个参数:域名,可以随便给一个//第二个参数:要分析的文本内容TokenStream tokenStream = analyzer.tokenStream("test",content);//添加一个引用,可以获得每个关键词CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class);//添加一个偏移量的引用,记录了关键词的开始位置以及结束位置OffsetAttribute offsetAttribute = tokenStream.addAttribute(OffsetAttribute.class);//将指针调整到列表的头部tokenStream.reset();//遍历关键词列表,通过incrementToken方法判断列表是否结束while(tokenStream.incrementToken()) {//关键词的起始位置System.out.println("start->" + offsetAttribute.startOffset());//取关键词System.out.println(charTermAttribute);//结束位置System.out.println("end->" + offsetAttribute.endOffset());}tokenStream.close();}public static void main(String[] args) throws Exception {//SearchIndex.search();queryParser();//testTokenStream("The Spring Framework provides a comprehensive programming and configuration model.");//testTokenStream("从初中的某一天开始,我突然之间觉得仰躺着睡觉睡着睡着会呼吸困难憋醒,然后我就只能侧卧来睡觉,开始是歪着头睡觉也可以的,后面就干脆侧卧睡觉,十几年了也不知道是怎么回事,一直以为是精神类的强迫症,所以也没怎么管,但总是不清楚啥原因。");}}
3、索引管理
IndexManager.java
package com.suibibk.lucene;import org.apache.lucene.document.Document;import org.apache.lucene.document.Field;import org.apache.lucene.document.StoredField;import org.apache.lucene.document.TextField;import org.apache.lucene.index.IndexWriter;import org.apache.lucene.index.IndexWriterConfig;import org.apache.lucene.index.Term;import org.apache.lucene.store.FSDirectory;import org.junit.Before;import org.junit.Test;import org.wltea.analyzer.lucene.IKAnalyzer;import java.io.File;public class IndexManager {private IndexWriter indexWriter;@Beforepublic void init() throws Exception {//创建一个IndexWriter对象,需要使用IKAnalyzer作为分析器indexWriter =new IndexWriter(FSDirectory.open(new File("F:\\Source\\Index").toPath()),new IndexWriterConfig(new IKAnalyzer()));}@Testpublic void addDocument() throws Exception {//创建一个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", "c:/temp/helo"));// 把文档写入索引库indexWriter.addDocument(document);//关闭索引库indexWriter.close();}@Testpublic void deleteAllDocument() throws Exception {//删除全部文档indexWriter.deleteAll();//关闭索引库indexWriter.close();}@Testpublic void deleteDocumentByQuery() throws Exception {indexWriter.deleteDocuments(new Term("name", "apache"));indexWriter.close();}@Testpublic void updateDocument() throws Exception {//创建一个新的文档对象Document document = new Document();//向文档对象中添加域document.add(new TextField("name", "更新之后的文档", Field.Store.YES));document.add(new TextField("name1", "更新之后的文档2", Field.Store.YES));document.add(new TextField("name2", "更新之后的文档3", Field.Store.YES));//更新操作indexWriter.updateDocument(new Term("name", "spring"), document);//关闭索引库indexWriter.close();}}
以及文档中有提到的分词查询。
好了,先入门熟悉!
