个人随笔
lucene全文检索入门
2020-10-13 21:14:05

一、什么是全文检索

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引入依赖如下:

  1. <!-- https://mvnrepository.com/artifact/junit/junit -->
  2. <dependency>
  3. <groupId>junit</groupId>
  4. <artifactId>junit</artifactId>
  5. <version>4.13</version>
  6. </dependency>
  7. <!-- lucene -->
  8. <!-- https://mvnrepository.com/artifact/org.apache.lucene/lucene-core -->
  9. <dependency>
  10. <groupId>org.apache.lucene</groupId>
  11. <artifactId>lucene-core</artifactId>
  12. <version>7.4.0</version>
  13. </dependency>
  14. <!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
  15. <dependency>
  16. <groupId>commons-io</groupId>
  17. <artifactId>commons-io</artifactId>
  18. <version>2.6</version>
  19. </dependency>
  20. <dependency>
  21. <groupId>org.wltea</groupId>
  22. <artifactId>IKAnalyzer</artifactId>
  23. <version>1.0</version>
  24. </dependency>
  25. <!-- https://mvnrepository.com/artifact/org.apache.lucene/lucene-queryparser -->
  26. <dependency>
  27. <groupId>org.apache.lucene</groupId>
  28. <artifactId>lucene-queryparser</artifactId>
  29. <version>7.4.0</version>
  30. </dependency>

上面IKAnalyzer-1.0.jar可能maven仓库中不存在,并且配置文件也没有,这里提供下下载链接:https://www.suibibk.com/fileupload/files/202010/IKAnalyzer.zip 有需要的可以直接下载。

1、创建索引

CreateIndex.java

  1. package com.suibibk.lucene;
  2. import java.io.File;
  3. import org.apache.commons.io.FileUtils;
  4. import org.apache.lucene.analysis.Analyzer;
  5. import org.apache.lucene.document.Document;
  6. import org.apache.lucene.document.Field;
  7. import org.apache.lucene.document.TextField;
  8. import org.apache.lucene.index.IndexWriter;
  9. import org.apache.lucene.index.IndexWriterConfig;
  10. import org.apache.lucene.store.Directory;
  11. import org.apache.lucene.store.FSDirectory;
  12. import org.wltea.analyzer.lucene.IKAnalyzer;
  13. /**
  14. * 创建索引
  15. * @author pc
  16. *
  17. */
  18. public class CreateIndex {
  19. public static void main(String[] args) throws Exception {
  20. Long start = System.currentTimeMillis();
  21. //第一步:创建一个java工程,并导入jar包。
  22. //第二步:创建一个indexwriter对象。
  23. //1)指定索引库的存放位置Directory对象
  24. //指定索引库存放的路径
  25. //D:\temp\index
  26. Directory directory = FSDirectory.open(new File("F:\\Source\\Index").toPath());
  27. //索引库还可以存放到内存中
  28. //Directory directory = new RAMDirectory();
  29. //2)指定一个IndexWriterConfig对象。
  30. //创建indexwriterCofig对象
  31. // IndexWriterConfig config = new IndexWriterConfig();
  32. //加入中文分词
  33. Analyzer analyzer = new IKAnalyzer();
  34. IndexWriterConfig config = new IndexWriterConfig(analyzer);
  35. //创建indexwriter对象
  36. IndexWriter indexWriter = new IndexWriter(directory, config);
  37. //原始文档的路径
  38. File dir = new File("E:\\data\\table\\contents");
  39. for (File f : dir.listFiles()) {
  40. //文件名
  41. String fileName = f.getName();
  42. //文件内容
  43. String fileContent = FileUtils.readFileToString(f, "UTF-8");
  44. //文件路径
  45. String filePath = f.getPath();
  46. //文件的大小
  47. long fileSize = FileUtils.sizeOf(f);
  48. //第三步:创建field对象
  49. //第一个参数:域的名称
  50. //第二个参数:域的内容
  51. //第三个参数:是否存储
  52. Field fileNameField = new TextField("filename", fileName, Field.Store.NO);
  53. //文件内容域
  54. Field fileContentField = new TextField("content", fileContent, Field.Store.NO);
  55. //文件路径域(不分析、不索引、只存储)
  56. Field filePathField = new TextField("path", filePath, Field.Store.YES);
  57. //文件大小域
  58. Field fileSizeField = new TextField("size", fileSize + "", Field.Store.NO);
  59. //第四步:创建document对象,将field添加到document对象中。
  60. Document document = new Document();
  61. document.add(fileNameField);
  62. document.add(fileContentField);
  63. document.add(filePathField);
  64. document.add(fileSizeField);
  65. //第四步:使用indexwriter对象将document对象写入索引库,此过程进行索引创建。并将索引和document对象写入索引库。
  66. indexWriter.addDocument(document);
  67. }
  68. //第五步:关闭IndexWriter对象。
  69. indexWriter.close();
  70. Long end = System.currentTimeMillis();
  71. System.out.println("创建索引耗时:"+(end-start)+"毫秒");
  72. }
  73. }

2、查询索引

SearchIndex.java

  1. package com.suibibk.lucene;
  2. import java.io.File;
  3. import org.apache.lucene.analysis.Analyzer;
  4. import org.apache.lucene.analysis.TokenStream;
  5. import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
  6. import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;
  7. import org.apache.lucene.document.Document;
  8. import org.apache.lucene.index.DirectoryReader;
  9. import org.apache.lucene.index.IndexReader;
  10. import org.apache.lucene.index.Term;
  11. import org.apache.lucene.queryparser.classic.QueryParser;
  12. import org.apache.lucene.search.IndexSearcher;
  13. import org.apache.lucene.search.Query;
  14. import org.apache.lucene.search.ScoreDoc;
  15. import org.apache.lucene.search.TermQuery;
  16. import org.apache.lucene.search.TopDocs;
  17. import org.apache.lucene.store.Directory;
  18. import org.apache.lucene.store.FSDirectory;
  19. import org.wltea.analyzer.lucene.IKAnalyzer;
  20. /**
  21. * 查询索引
  22. * @author pc
  23. *
  24. */
  25. public class SearchIndex {
  26. public static void search() throws Exception {
  27. //指定索引库存放的路径
  28. //D:\temp\index
  29. Directory directory = FSDirectory.open(new File("F:\\Source\\Index").toPath());
  30. //创建indexReader对象
  31. IndexReader indexReader = DirectoryReader.open(directory);
  32. //创建indexsearcher对象
  33. IndexSearcher indexSearcher = new IndexSearcher(indexReader);
  34. //创建查询
  35. Query query = new TermQuery(new Term("content", "CentOS7下载"));
  36. //执行查询
  37. //第一个参数是查询对象,第二个参数是查询结果返回的最大值
  38. TopDocs topDocs = indexSearcher.search(query, 10);
  39. //查询结果的总条数
  40. System.out.println("查询结果的总条数:"+ topDocs.totalHits);
  41. //遍历查询结果
  42. //topDocs.scoreDocs存储了document对象的id
  43. for (ScoreDoc scoreDoc : topDocs.scoreDocs) {
  44. //scoreDoc.doc属性就是document对象的id
  45. //根据document的id找到document对象
  46. Document document = indexSearcher.doc(scoreDoc.doc);
  47. System.out.println(document.get("filename"));
  48. //System.out.println(document.get("content"));
  49. System.out.println(document.get("path"));
  50. System.out.println(document.get("size"));
  51. System.out.println("-------------------------");
  52. }
  53. //关闭indexreader对象
  54. indexReader.close();
  55. }
  56. public static void queryParser() throws Exception {
  57. //指定索引库存放的路径
  58. //D:\temp\index
  59. Directory directory = FSDirectory.open(new File("F:\\Source\\Index").toPath());
  60. //创建indexReader对象
  61. IndexReader indexReader = DirectoryReader.open(directory);
  62. //创建indexsearcher对象
  63. IndexSearcher indexSearcher = new IndexSearcher(indexReader);
  64. QueryParser queryParser = new QueryParser("content", new IKAnalyzer());
  65. Query query = queryParser.parse("Lucene是java开发的");
  66. //执行查询
  67. //第一个参数是查询对象,第二个参数是查询结果返回的最大值
  68. TopDocs topDocs = indexSearcher.search(query, 10);
  69. //查询结果的总条数
  70. System.out.println("查询结果的总条数:"+ topDocs.totalHits);
  71. //遍历查询结果
  72. //topDocs.scoreDocs存储了document对象的id
  73. for (ScoreDoc scoreDoc : topDocs.scoreDocs) {
  74. //scoreDoc.doc属性就是document对象的id
  75. //根据document的id找到document对象
  76. Document document = indexSearcher.doc(scoreDoc.doc);
  77. System.out.println(document.get("filename"));
  78. //System.out.println(document.get("content"));
  79. System.out.println(document.get("path"));
  80. System.out.println(document.get("size"));
  81. System.out.println("-------------------------");
  82. }
  83. //关闭indexreader对象
  84. indexReader.close();
  85. }
  86. public static void testTokenStream(String content) throws Exception {
  87. //创建一个标准分析器对象
  88. // Analyzer analyzer = new StandardAnalyzer();
  89. Analyzer analyzer = new IKAnalyzer();
  90. //获得tokenStream对象
  91. //第一个参数:域名,可以随便给一个
  92. //第二个参数:要分析的文本内容
  93. TokenStream tokenStream = analyzer.tokenStream("test",content);
  94. //添加一个引用,可以获得每个关键词
  95. CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class);
  96. //添加一个偏移量的引用,记录了关键词的开始位置以及结束位置
  97. OffsetAttribute offsetAttribute = tokenStream.addAttribute(OffsetAttribute.class);
  98. //将指针调整到列表的头部
  99. tokenStream.reset();
  100. //遍历关键词列表,通过incrementToken方法判断列表是否结束
  101. while(tokenStream.incrementToken()) {
  102. //关键词的起始位置
  103. System.out.println("start->" + offsetAttribute.startOffset());
  104. //取关键词
  105. System.out.println(charTermAttribute);
  106. //结束位置
  107. System.out.println("end->" + offsetAttribute.endOffset());
  108. }
  109. tokenStream.close();
  110. }
  111. public static void main(String[] args) throws Exception {
  112. //SearchIndex.search();
  113. queryParser();
  114. //testTokenStream("The Spring Framework provides a comprehensive programming and configuration model.");
  115. //testTokenStream("从初中的某一天开始,我突然之间觉得仰躺着睡觉睡着睡着会呼吸困难憋醒,然后我就只能侧卧来睡觉,开始是歪着头睡觉也可以的,后面就干脆侧卧睡觉,十几年了也不知道是怎么回事,一直以为是精神类的强迫症,所以也没怎么管,但总是不清楚啥原因。");
  116. }
  117. }

3、索引管理

IndexManager.java

  1. package com.suibibk.lucene;
  2. import org.apache.lucene.document.Document;
  3. import org.apache.lucene.document.Field;
  4. import org.apache.lucene.document.StoredField;
  5. import org.apache.lucene.document.TextField;
  6. import org.apache.lucene.index.IndexWriter;
  7. import org.apache.lucene.index.IndexWriterConfig;
  8. import org.apache.lucene.index.Term;
  9. import org.apache.lucene.store.FSDirectory;
  10. import org.junit.Before;
  11. import org.junit.Test;
  12. import org.wltea.analyzer.lucene.IKAnalyzer;
  13. import java.io.File;
  14. public class IndexManager {
  15. private IndexWriter indexWriter;
  16. @Before
  17. public void init() throws Exception {
  18. //创建一个IndexWriter对象,需要使用IKAnalyzer作为分析器
  19. indexWriter =
  20. new IndexWriter(FSDirectory.open(new File("F:\\Source\\Index").toPath()),
  21. new IndexWriterConfig(new IKAnalyzer()));
  22. }
  23. @Test
  24. public void addDocument() throws Exception {
  25. //创建一个Document对象
  26. Document document = new Document();
  27. //向document对象中添加域
  28. document.add(new TextField("name", "新添加的文件", Field.Store.YES));
  29. document.add(new TextField("content", "新添加的文件内容", Field.Store.NO));
  30. document.add(new StoredField("path", "c:/temp/helo"));
  31. // 把文档写入索引库
  32. indexWriter.addDocument(document);
  33. //关闭索引库
  34. indexWriter.close();
  35. }
  36. @Test
  37. public void deleteAllDocument() throws Exception {
  38. //删除全部文档
  39. indexWriter.deleteAll();
  40. //关闭索引库
  41. indexWriter.close();
  42. }
  43. @Test
  44. public void deleteDocumentByQuery() throws Exception {
  45. indexWriter.deleteDocuments(new Term("name", "apache"));
  46. indexWriter.close();
  47. }
  48. @Test
  49. public void updateDocument() throws Exception {
  50. //创建一个新的文档对象
  51. Document document = new Document();
  52. //向文档对象中添加域
  53. document.add(new TextField("name", "更新之后的文档", Field.Store.YES));
  54. document.add(new TextField("name1", "更新之后的文档2", Field.Store.YES));
  55. document.add(new TextField("name2", "更新之后的文档3", Field.Store.YES));
  56. //更新操作
  57. indexWriter.updateDocument(new Term("name", "spring"), document);
  58. //关闭索引库
  59. indexWriter.close();
  60. }
  61. }

以及文档中有提到的分词查询。

好了,先入门熟悉!

 11
上一篇: 无
下一篇: 无

啊!这个可能是世界上最丑的留言输入框功能~


当然,也是最丑的留言列表

有疑问发邮件到 : suibibk@qq.com 侵权立删
Copyright : 个人随笔   备案号 : 粤ICP备18099399号-2