Lucene 基础知识

第一、什么是全文检索

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

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

1.2 结构化数据搜索

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

1.3 非结构化数据搜索

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

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

1.4 如何实现全文检索

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

1.5 全文检索应用场景

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

 

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

2.1 Lucene 索引和搜索流程图

Lucene 基础知识_第1张图片

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

2.2 Lucene 创建索引

实现步骤总结:

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

pom.xml 添加相关依赖:

        
		
			org.apache.lucene
			lucene-core
			7.4.0
		
		
			org.apache.lucene
			lucene-analyzers-common
			7.4.0
		
		
		
			commons-fileupload
			commons-fileupload
			1.3.1
		

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 包

        
			org.apache.lucene
			lucene-analyzers-smartcn
			7.4.0
		

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

Lucene 基础知识_第2张图片

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

  
             org.wltea.ik-analyzer
        	 ik-analyzer
             8.4.0
            
            
                
                    org.apache.lucene
                    lucene-core
                
                
                    org.apache.lucene
                    lucene-queryparser
                
                
                    org.apache.lucene
                    lucene-analyzers-common
                
            
        

第三、Lucene 索引库维护

3.1 索引库的添加

3.1.1 Field域的属性

是否分析:是否对域的内容进行分词处理。前提是我们要对域的内容进行查询。

是否索引:将Field分析后的词或整个Field值进行索引,只有索引方可搜索到。
比如:商品名称、商品简介分析后进行索引,订单号、身份证号不用分析但也要索引,这些将来都要作为查询条件。

是否存储:将Field值存储在文档中,存储在文档中的Field才可以从Document中获取
比如:商品名称、订单号,凡是将来要从Document中获取的Field都要存储。

注意:是否存储的标准:是否要将内容展示给用户

Lucene 基础知识_第3张图片

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包。

       
			org.apache.lucene
			lucene-queryparser
			7.4.0
		
@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();
}

 

你可能感兴趣的:(Java架构专栏)