Lucene 基础知识

符号 阅读:618 2021-03-31 14:13:01 评论:0

第一、什么是全文检索

1.1 数据分类(结构化和非结构化数据)

结构化数据:指具有固定格式或有限长度的数据,如数据库,元数据等。
非结构化数据:指不定长或无固定格式的数据,如邮件,word文档等磁盘上的文件

1.2 结构化数据搜索

常见的结构化数据也就是数据库中的数据。数据检索通常使用SQL

1.3 非结构化数据搜索

(1)顺序扫描法:所谓顺序扫描,比如要找内容包含某一个字符串的文件,就是一个文档一个文档的看,对于每一个文档,从头看到尾,如果此文档包含此字符串,则此文档为我们要找的文件,接着看下一个文件,直到扫描完所有的文件。如利用windows的搜索也可以搜索文件内容,只是相当的慢。

(2)全文检索:将非结构化数据中的一部分信息提取出来,重新组织,使其变得有一定结构,然后对此有一定结构的数据进行搜索,从而达到搜索相对较快的目的。这部分从非结构化数据中提取出的然后重新组织的信息,我们称之索引。如字典的拼音表和部首检字表就相当于字典的索引。

1.4 如何实现全文检索

      Lucene是apache下的一个开放源代码的全文检索引擎工具包。提供了完整的查询引擎和索引引擎,部分文本分析引擎。Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能。

1.5 全文检索应用场景

       对于数据量大、数据结构不固定的数据可采用全文检索方式搜索,比如百度、Google等搜索引擎、论坛站内搜索、电商网站站内搜索等。

 

第二、Lucene 全文检索流程和实现

2.1 Lucene 索引和搜索流程图

1、绿色表示索引过程,对要搜索的原始内容进行索引构建一个索引库,索引过程包括:
确定原始内容即要搜索的内容采集文档创建文档分析文档索引文档
    
2、红色表示搜索过程,从索引库中搜索内容,搜索过程包括:
用户通过搜索界面创建查询执行搜索,从索引库搜索渲染搜索结果

2.2 Lucene 创建索引

实现步骤总结:

第一步:创建一个java工程,并导入jar包。 
第二步:创建一个indexwriter对象。 
1)指定索引库的存放位置Directory对象 
2)指定一个IndexWriterConfig对象。 
第三步:创建document对象。 
第四步:创建field对象,将field添加到document对象中。 
第五步:使用indexwriter对象将document对象写入索引库,此过程进行索引创建。并将索引和document对象写入索引库。 
第六步:关闭IndexWriter对象。

pom.xml 添加相关依赖:

        <!--lucene jar包 --> 
		<dependency> 
			<groupId>org.apache.lucene</groupId> 
			<artifactId>lucene-core</artifactId> 
			<version>7.4.0</version> 
		</dependency> 
		<dependency> 
			<groupId>org.apache.lucene</groupId> 
			<artifactId>lucene-analyzers-common</artifactId> 
			<version>7.4.0</version> 
		</dependency> 
		<!-- apache common 通用工具类 --> 
		<dependency> 
			<groupId>commons-fileupload</groupId> 
			<artifactId>commons-fileupload</artifactId> 
			<version>1.3.1</version> 
		</dependency>

java 示例代码:

package com.zzg.lucene; 
 
import java.io.File; 
import java.io.IOException; 
 
import org.apache.commons.io.FileUtils; 
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; 
 
/** 
 * 索引创建 
 * @author Administrator 
 * 
 */ 
public class IndexCreate { 
 
	public static void main(String[] args) throws Exception { 
		 //指定索引库存放的路径 
	    //D:\temp\index 
	    Directory directory = FSDirectory.open(new File("D:\\temp\\index").toPath()); 
	    //索引库还可以存放到内存中 
	    //Directory directory = new RAMDirectory(); 
	    //创建indexwriterCofig对象 
	    IndexWriterConfig config = new IndexWriterConfig(); 
	    //创建indexwriter对象 
	    IndexWriter indexWriter = new IndexWriter(directory, config); 
	    //原始文档的路径 
	    File dir = new File("D:\\baiduNetdiskDownload\\searchsource"); 
	    for (File f : dir.listFiles()) { 
	        //文件名 
	        String fileName = f.getName(); 
	        //文件内容 
	        String fileContent = FileUtils.readFileToString(f); 
	        //文件路径 
	        String filePath = f.getPath(); 
	        //文件的大小 
	        long fileSize  = FileUtils.sizeOf(f); 
	        //创建文件名域 
	        //第一个参数:域的名称 
	        //第二个参数:域的内容 
	        //第三个参数:是否存储 
	        Field fileNameField = new TextField("filename", fileName, Field.Store.YES); 
	        //文件内容域 
	        Field fileContentField = new TextField("content", fileContent, Field.Store.YES); 
	        //文件路径域(不分析、不索引、只存储) 
	        Field filePathField = new TextField("path", filePath, Field.Store.YES); 
	        //文件大小域 
	        Field fileSizeField = new TextField("size", fileSize + "", Field.Store.YES); 
 
	        //创建document对象 
	        Document document = new Document(); 
	        document.add(fileNameField); 
	        document.add(fileContentField); 
	        document.add(filePathField); 
	        document.add(fileSizeField); 
	        //创建索引,并写入索引库 
	        indexWriter.addDocument(document); 
	    } 
	    //关闭indexwriter 
	    indexWriter.close(); 
	} 
 
} 

2.3 Lucene 查询索引

实现步骤总结:

第一步:创建一个Directory对象,也就是索引库存放的位置。 
第二步:创建一个indexReader对象,需要指定Directory对象。 
第三步:创建一个indexsearcher对象,需要指定IndexReader对象 
第四步:创建一个TermQuery对象,指定查询的域和查询的关键词。 
第五步:执行查询。 
第六步:返回查询结果。遍历查询结果并输出。 
第七步:关闭IndexReader对象

java 示例代码:

package com.zzg.lucene; 
 
import java.io.File; 
import java.io.IOException; 
 
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.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; 
 
/** 
 * 查询索引 
 * @author Administrator 
 * 
 */ 
public class SearchIndex { 
 
	public static void main(String[] args) throws Exception { 
		//指定索引库存放的路径 
	    //D:\temp\index 
	    Directory directory = FSDirectory.open(new File("D:\\temp\\index").toPath()); 
	    //创建indexReader对象 
	    IndexReader indexReader = DirectoryReader.open(directory); 
	    //创建indexsearcher对象 
	    IndexSearcher indexSearcher = new IndexSearcher(indexReader); 
	    //创建查询 
	    Query query = new TermQuery(new Term("filename", "apache")); 
	    //执行查询 
	    //第一个参数是查询对象,第二个参数是查询结果返回的最大值 
	    TopDocs topDocs = indexSearcher.search(query, 10); 
	    //查询结果的总条数 
	    System.out.println("查询结果的总条数:"+ topDocs.totalHits); 
	    //遍历查询结果 
	    //topDocs.scoreDocs存储了document对象的id 
	    for (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(); 
 
	} 
 
} 

2.4 Lucene 标准分词器(StandardAnalyzer 和SmartChineseAnalyzer)

(1)、StandardAnalyzer:
单字分词:就是按照中文一个字一个字地进行分词。如:“我爱中国”,
效果:“我”、“爱”、“中”、“国”。
(2)、SmartChineseAnalyzer
对中文支持较好,但扩展性差,扩展词库,禁用词库和同义词库等不好处理

 

pom.xml 文件添加lucene-analyzers-smartcn.jar 包

        <dependency> 
			<groupId>org.apache.lucene</groupId> 
			<artifactId>lucene-analyzers-smartcn</artifactId> 
			<version>7.4.0</version> 
		</dependency>

java 示例代码:  

package com.zzg.lucene; 
 
import java.io.IOException; 
 
import org.apache.lucene.analysis.Analyzer; 
import org.apache.lucene.analysis.TokenStream; 
import org.apache.lucene.analysis.standard.StandardAnalyzer; 
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute; 
 
/** 
 * 标准分词创建:StandardAnalyzer 
 *  
 * @author Administrator 
 * 
 */ 
public class StandardAnalyzerCreate { 
 
	public static void main(String[] args) throws IOException { 
		String english = "Analysis is one of the main causes of slow indexing. Simply put, the more you analyze the slower analyze the indexing (in most cases)."; 
		String chinese = "张三说的确实在理。"; 
 
		Analyzer analyzer = new StandardAnalyzer(); 
		 
		TokenStream tokenStream = analyzer.tokenStream("english", english); 
		doToken(tokenStream); 
		 
		tokenStream = analyzer.tokenStream("chinese", chinese); 
		doToken(tokenStream); 
	} 
 
	private static void doToken(TokenStream tokenStream) throws IOException { 
		tokenStream.reset(); 
		CharTermAttribute cta = tokenStream.getAttribute(CharTermAttribute.class); 
		while (tokenStream.incrementToken()) { 
			System.out.print(cta.toString() + "|"); 
		} 
		tokenStream.end(); 
		tokenStream.close(); 
	} 
 
} 
package com.zzg.lucene; 
 
import java.io.IOException; 
 
import org.apache.lucene.analysis.Analyzer; 
import org.apache.lucene.analysis.TokenStream; 
import org.apache.lucene.analysis.cn.smart.SmartChineseAnalyzer; 
import org.apache.lucene.analysis.standard.StandardAnalyzer; 
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute; 
 
/** 
 * 标准分词创建:SmartChineseAnalyzer 
 * @author Administrator 
 * 
 */ 
public class SmartChineseAnalyzerCreate { 
 
	public static void main(String[] args) throws IOException { 
		String english = "Analysis is one of the main causes of slow indexing. Simply put, the more you analyze the slower analyze the indexing (in most cases)."; 
		String chinese = "张三说的确实在理。"; 
 
		Analyzer analyzer = new SmartChineseAnalyzer(); 
		 
		TokenStream tokenStream = analyzer.tokenStream("english", english); 
		doToken(tokenStream); 
		 
		tokenStream = analyzer.tokenStream("chinese", chinese); 
		doToken(tokenStream); 
	} 
 
	private static void doToken(TokenStream tokenStream) throws IOException { 
		tokenStream.reset(); 
		CharTermAttribute cta = tokenStream.getAttribute(CharTermAttribute.class); 
		while (tokenStream.incrementToken()) { 
			System.out.print(cta.toString() + "|"); 
		} 
		tokenStream.end(); 
		tokenStream.close(); 
	} 
} 

2.5 IKAnalyzer

使用方法:

第一步:把jar包添加到工程中

第二步:把配置文件和扩展词典和停用词词典添加到classpath下

 

意:hotword.dic和ext_stopword.dic文件的格式为UTF-8,注意是无BOM 的UTF-8 编码。

也就是说禁止使用windows记事本编辑扩展词典文件

java 示例代码:

package com.zzg.lucene; 
 
import java.io.IOException; 
import java.io.StringReader; 
 
import org.apache.lucene.analysis.Analyzer; 
import org.apache.lucene.analysis.TokenStream; 
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute; 
import org.wltea.analyzer.lucene.IKAnalyzer; 
 
public class IKAnalyzerCreate { 
 
	public static void main(String[] args) throws IOException { 
		String chinese = "基于java语言开发的轻量级的中文分词工具包"; 
		StringReader reader = new StringReader(chinese); 
		Analyzer analyzer = new IKAnalyzer(true); 
		 
		 
		TokenStream tokenStream = analyzer.tokenStream("chinese", reader); 
		doToken(tokenStream); 
	} 
 
	private static void doToken(TokenStream tokenStream) throws IOException { 
		tokenStream.reset(); 
		CharTermAttribute cta = tokenStream.getAttribute(CharTermAttribute.class); 
		while (tokenStream.incrementToken()) { 
			System.out.print(cta.toString() + "|"); 
		} 
		tokenStream.end(); 
		tokenStream.close(); 
	} 
 
} 

注意: ikanalyzer 分词对Lucene 版本的支持仅到Lucene 4.x版本,使用lucene 7.x 高版本集成ikanalyzer 分词会提示如下错误信息:

Exception in thread "main" java.lang.AbstractMethodError: org.apache.lucene.analysis.Analyzer.createComponents(Ljava/lang/String;)Lorg/apache/lucene/analysis/Analyzer$TokenStreamComponents; 
    at org.apache.lucene.analysis.Analyzer.tokenStream(Analyzer.java:162) 
    at com.inspur.analysis.kg.util.IKAnalyzerTest.main(IKAnalyzerTest.java:22)

解决办法:其他开发爱好者维护的ik-analyzer版本,支持Lucene5/6/7/8+版本, 且长期维护。

1、github 下载ik-analyzer 源码,进行本地编译安装。

源码下载地址:https://github.com/blueshen/ik-analyzer

2、Eclipse 导入ik-analyzer 源码,更新pom.xml 文件依赖。

3、打开windows 的cmd 窗口指令,切换之ik-analyzer 所在目录,执行如下指令:

mvn clean install -Dmaven.test.skip=true

java 示例代码:集成IK 分词的pom 文件依赖:

  <dependency> 
             <groupId>org.wltea.ik-analyzer</groupId> 
        	 <artifactId>ik-analyzer</artifactId> 
             <version>8.4.0</version> 
            <!--排除掉里面旧的lucene包,因为我们要重写里面的分析器和分词器  --> 
            <exclusions> 
                <exclusion> 
                    <groupId>org.apache.lucene</groupId> 
                    <artifactId>lucene-core</artifactId> 
                </exclusion> 
                <exclusion> 
                    <groupId>org.apache.lucene</groupId> 
                    <artifactId>lucene-queryparser</artifactId> 
                </exclusion> 
                <exclusion> 
                    <groupId>org.apache.lucene</groupId> 
                    <artifactId>lucene-analyzers-common</artifactId> 
                </exclusion> 
            </exclusions> 
        </dependency>

第三、Lucene 索引库维护

3.1 索引库的添加

3.1.1 Field域的属性

是否分析:是否对域的内容进行分词处理。前提是我们要对域的内容进行查询。 
 
是否索引:将Field分析后的词或整个Field值进行索引,只有索引方可搜索到。 
比如:商品名称、商品简介分析后进行索引,订单号、身份证号不用分析但也要索引,这些将来都要作为查询条件。 
 
是否存储:将Field值存储在文档中,存储在文档中的Field才可以从Document中获取 
比如:商品名称、订单号,凡是将来要从Document中获取的Field都要存储。 
 
注意:是否存储的标准:是否要将内容展示给用户

3.1.2 文档添加

    //索引库存放路径 
    Directory directory = FSDirectory.open(new File("D:\\temp\\index").toPath()); 
    IndexWriterConfig config = new IndexWriterConfig(new IKAnalyzer()); 
    //创建一个indexwriter对象 
    IndexWriter indexWriter = new IndexWriter(directory, config); 
    //创建一个Document对象 
    Document document = new Document(); 
    //向document对象中添加域。 
    //不同的document可以有不同的域,同一个document可以有相同的域。 
    document.add(new TextField("filename", "新添加的文档", Field.Store.YES)); 
    document.add(new TextField("content", "新添加的文档的内容", Field.Store.NO)); 
    //LongPoint创建索引 
    document.add(new LongPoint("size", 1000l)); 
    //StoreField存储数据 
    document.add(new StoredField("size", 1000l)); 
    //不需要创建索引的就使用StoreField存储 
    document.add(new StoredField("path", "d:/temp/1.txt")); 
    //添加文档到索引库 
    indexWriter.addDocument(document); 
    //关闭indexwriter 
    indexWriter.close();

3.2 索引库删除

3.2.1 索引库全部删除

         //索引库存放路径 
        Directory directory = FSDirectory.open(new File("D:\\temp\\index").toPath()); 
        IndexWriterConfig config = new IndexWriterConfig(new IKAnalyzer()); 
        //创建一个indexwriter对象 
        IndexWriter indexWriter = new IndexWriter(directory, config); 
		//删除全部索引 
		indexWriter.deleteAll(); 
		//关闭indexwriter 
		indexWriter.close();

 说明:将索引目录的索引信息全部删除,直接彻底删除,无法恢复。

3.2.2 索引库指定条件删除

//索引库存放路径 
Directory directory = FSDirectory.open(new File("D:\\temp\\index").toPath()); 
IndexWriterConfig config = new IndexWriterConfig(new IKAnalyzer()); 
//创建一个indexwriter对象 
IndexWriter indexWriter = new IndexWriter(directory, config); 
//创建一个查询条件 
Query query = new TermQuery(new Term("filename", "apache")); 
//根据查询条件删除 
indexWriter.deleteDocuments(query); 
//关闭indexwriter 
indexWriter.close();

3.3 索引库修改

//索引库存放路径 
Directory directory = FSDirectory.open(new File("D:\\temp\\index").toPath()); 
IndexWriterConfig config = new IndexWriterConfig(new IKAnalyzer()); 
//创建一个indexwriter对象 
IndexWriter indexWriter = new IndexWriter(directory, config); 
//创建一个Document对象 
Document document = new Document(); 
//向document对象中添加域。 
//不同的document可以有不同的域,同一个document可以有相同的域。 
document.add(new TextField("filename", "要更新的文档", Field.Store.YES)); 
document.add(new TextField("content", " Lucene 简介 Lucene 是一个基于 Java 的全文信息检索工具包," + "它不是一个完整的搜索应用程序,而是为你的应用程序提供索引和搜索功能。",Field.Store.YES)); 
indexWriter.updateDocument(new Term("content", "java"), document); 
//关闭indexWriter 
indexWriter.close();

第四、Lucene 索引库查询

           对要搜索的信息创建Query查询对象,Lucene会根据Query查询对象生成最终的查询语法,类似关系数据库Sql语法一样Lucene也有自己的查询语法,比如:“name:lucene”表示查询Field的name为“lucene”的文档信息。
    可通过两种方法创建查询对象:
    1)使用Lucene提供Query子类
    2)使用QueryParse解析查询表达式

4.1 TermQuery

TermQuery,通过项查询,TermQuery不使用分析器所以建议匹配不分词的Field域查询,比如订单号、分类ID号等。
指定要查询的域和要查询的关键词。

Directory directory = FSDirectory.open(new File("D:\\temp\\index").toPath()); 
IndexReader indexReader = DirectoryReader.open(directory); 
IndexSearcher indexSearcher = new IndexSearcher(indexReader); 
     
//创建查询对象 
Query query = new TermQuery(new Term("content", "lucene")); 
//执行查询 
TopDocs topDocs = indexSearcher.search(query, 10); 
//共查询到的document个数 
System.out.println("查询结果总数量:" + topDocs.totalHits); 
//遍历查询结果 
for (ScoreDoc scoreDoc : topDocs.scoreDocs) { 
        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")); 
      } 
 //关闭indexreader 
 indexSearcher.getIndexReader().close();

4.2 数值范围查询

Query query = LongPoint.newRangeQuery("size", 0l, 10000l);

4.3 使用queryparser查询

       通过QueryParser也可以创建Query,QueryParser提供一个Parse方法,此方法可以直接根据查询语法来查询。Query对象执行的查询语法可通过System.out.println(query);查询。需要使用到分析器。建议创建索引时使用的分析器和查询索引时使用的分析器要一致。需要加入queryParser依赖的jar包。

       <dependency> 
			<groupId>org.apache.lucene</groupId> 
			<artifactId>lucene-queryparser</artifactId> 
			<version>7.4.0</version> 
		</dependency>
@Test 
public void testQueryParser() throws Exception { 
    Directory directory = FSDirectory.open(new File("D:\\temp\\index").toPath()); 
    IndexReader indexReader = DirectoryReader.open(directory); 
    IndexSearcher indexSearcher = new IndexSearcher(indexReader); 
    //创建queryparser对象 
    //第一个参数默认搜索的域 
    //第二个参数就是分析器对象 
    QueryParser queryParser = new QueryParser("content", new IKAnalyzer()); 
    Query query = queryParser.parse("Lucene是java开发的"); 
    //执行查询 
    printResult(query, indexSearcher); 
} 
 
private void printResult(Query query, IndexSearcher indexSearcher) throws Exception { 
    //执行查询 
    TopDocs topDocs = indexSearcher.search(query, 10); 
    //共查询到的document个数 
    System.out.println("查询结果总数量:" + topDocs.totalHits); 
    //遍历查询结果 
    for (ScoreDoc scoreDoc : topDocs.scoreDocs) { 
        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")); 
    } 
    //关闭indexreader 
    indexSearcher.getIndexReader().close(); 
}

 

声明

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

关注我们

一个IT知识分享的公众号