Lucene自学之路(四)—— 索引的搜索

版权声明:本文为博主原创文章,请尊重原创,未经博主允许禁止转载,保留追究权 https://blog.csdn.net/qq_29914837/article/details/90516786

上章节已经讲解了索引的创建。不清楚看第三章节即可。
索引创建的目的是为了为后续的全文检索提供支持,方便更快捷找出需要的信息。
比如我需要找出一篇具有Lucene关键字的信息,传统的方式可以使用like 来实现,但是正在实际操作中这是不可能的

如果是百万级的数据,可想而知,效率是有多么底下,而且如果是需要在多表检索哪怎么实现了!

一、提供LuceneUtils工具类,包含索引操作核心对象,不需要每次还要创建

package com.springboot.main.eimm.search.util;

import java.io.File;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
import org.wltea.analyzer.lucene.IKAnalyzer;

/**
 * 
 * @method Lucene索引操作对象工具类
 * @author Mr yi
 * @time 2019年5月23日
 */
public class LuceneUtils {

    private static Directory directory  = null;

    private static IndexWriterConfig indexWriterConfig = null; 

    private static Analyzer analyzer = null;
    
    private static Version matchVersion = null;
    
    //static 代码块随着类的加载,只加载一次。作用是初始化类。
    static{
        try {
        	//在 6.6 以上版本中 version 不再是必要的,并且,存在无参构造方法,可以直接使用默认的 StandardAnalyzer 分词器。
            matchVersion = Version.LUCENE_8_1_0;
            //索引存放的位置,设置在当前目录中(项目根路径下)
            final String INDEXURL = "./index_dir/search";
            directory = FSDirectory.open(new File(INDEXURL).toPath());
            //analyzer = new StandardAnalyzer(); // 标准分词器,适用于英文[支持中文采用的方法为单字切分。他会将词汇单元转换成小写形式,并去除停用词和标点符号]
            //analyzer = new SmartChineseAnalyzer();//中文分词
            //analyzer = new ComplexAnalyzer();//中文分词
        	//analyzer = new IKAnalyzer();//中文分词
             analyzer = new IKAnalyzer();//中文分词
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static Directory getDirectory() {
        return directory;
    }

    /**
     * 
     * @method 返回用于操作索引的对象
     * @author Mr yi
     * @time 2019年5月23日
     * @return
     * @throws Exception
     */
    public static IndexWriter getIndexWriter() throws Exception{
    	//创建索引写入配置
        indexWriterConfig = new IndexWriterConfig(analyzer);
        //创建索引写入对象
        IndexWriter indexWriter = new IndexWriter(directory, indexWriterConfig);
        return indexWriter;
    }

    /**
     * 
     * @method 返回用于读取索引的对象
     * @author Mr yi
     * @time 2019年5月23日
     * @return
     * @throws Exception
     */
    public static IndexSearcher getIndexSearcher() throws Exception{
        IndexReader indexReader  = DirectoryReader.open(directory);
        IndexSearcher indexSearcher = new IndexSearcher(indexReader);
        return indexSearcher;
    }

    /**
     * 
     * @method 返回当前版本
     * @author Mr yi
     * @time 2019年5月23日
     * @return
     */
    public static Version getMatchVersion() {
        return matchVersion;
    }

    /**
     * 
     * @method 返回当前使用的分词器
     * @author Mr yi
     * @time 2019年5月23日
     * @return
     */
    public static Analyzer getAnalyzer() {
        return analyzer;
    }
    
    
  
 
}

二、SearchUtils核心类,提供操作索引的相关方法,我这里基本列出大部分常用的方法,按照自己项目需求修改即可使用。

package com.springboot.main.eimm.search.util;

import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.commons.collections.MapUtils;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryparser.classic.MultiFieldQueryParser;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.FuzzyQuery;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.PhraseQuery;
import org.apache.lucene.search.PrefixQuery;
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.search.WildcardQuery;
import org.apache.lucene.search.highlight.Formatter;
import org.apache.lucene.search.highlight.Highlighter;
import org.apache.lucene.search.highlight.QueryScorer;
import org.apache.lucene.search.highlight.SimpleFragmenter;
import org.apache.lucene.search.highlight.SimpleHTMLFormatter;
import com.springboot.main.eimm.search.entity.Search;
import com.springboot.main.function.article.entity.Article;

public class SearchUtils {

	/**
	 * 
	 * @method 将Article数据转换为Document
	 * @author Mr yi
	 * @time 2019年5月23日
	 * @param article 对象
	 * @return
	 */
	public static Document articleToDocument(Article article) {

		if (article == null)
			return null;

		Document document = new Document();
		IndexableField idField = new StringField("id", article.getId(), Store.YES);
		IndexableField titleField = new StringField("artitle_title", article.getArtitle_title(), Store.YES);
		IndexableField contentField = new TextField("artitle_content", article.getArtitle_content(), Store.YES);
		IndexableField urlField = new StringField("artitle_url", article.getArtitle_url(), Store.YES);
		IndexableField authorField = new StringField("artitle_author", article.getArtitle_author(), Store.YES);
		IndexableField stateField = new StringField("artitle_state", article.getArtitle_state(), Store.YES);
		IndexableField typeField = new StringField("artitle_type", article.getArtitle_type(), Store.YES);

		document.add(idField);
		document.add(titleField);
		document.add(contentField);
		document.add(urlField);
		document.add(authorField);
		document.add(stateField);
		document.add(typeField);
		return document;
	}

	/**
	 * 
	 * @method 将map数据转换为Document
	 * @author Mr yi
	 * @time 2019年5月23日
	 * @param map    存放一个对象具有的数据
	 * @param search 保存字段信息
	 * @return
	 */
	public static Document mapToDocument(Map<String, Object> map, Search search) {

		if (search == null)
			return null;
		Document document = new Document();
		// doc.add(new IntPoint("id", id));
		IndexableField idField = new StringField("id", MapUtils.getString(map, "id", ""), Store.YES);
		IndexableField titleField = new StringField("title", MapUtils.getString(map, search.getTitle(), ""), Store.YES);
		IndexableField contentField = new TextField("content", MapUtils.getString(map, search.getContent(), ""),
				Store.YES);
		IndexableField authorField = new StringField("author", MapUtils.getString(map, search.getAuthor(), ""),
				Store.YES);
		IndexableField typeField = new StringField("type", search.getType(), Store.YES);
		IndexableField dateField = new StringField("create_time", MapUtils.getString(map, search.getCreate_time(), ""),
				Store.YES);

		document.add(idField);
		document.add(titleField);
		document.add(contentField);
		document.add(authorField);
		document.add(typeField);
		document.add(dateField);
		return document;
	}

	/**
	 * 
	 * @method 添加索引
	 * @author Mr yi
	 * @time 2019年5月24日
	 * @param document
	 * @throws Exception
	 */
	public static void addIndex(Document document) throws Exception {
		// 获取indexWrite对象
		IndexWriter indexWriter = LuceneUtils.getIndexWriter();
		try {
			// 将document写入磁盘中
			indexWriter.addDocument(document);
		} finally {// 定要注意关闭indexWrite. 包括异常下,用finally关闭.否则会导致下一次写索引失败.,修改程序后,直接删除write.lock文件后就可以
			indexWriter.close();
		}

	}

	/**
	 * 
	 * @method 搜索索引(高亮显示文字)
	 * @author Mr yi
	 * @time 2019年5月23日
	 * @param keywords 搜索关键词
	 * @return
	 * @throws Exception
	 */
	public static List<Search> findIndex(String keywords) throws Exception {
		List<Search> searchs = new ArrayList<Search>();
		// Lucene的搜索方法
		IndexSearcher indexSearcher = LuceneUtils.getIndexSearcher();
		// 需要检索的字段
		String fields[] = { "title", "content", "author", "create_time" };
		// 在Lucene中查询的方式还有很多,这里使用简单的MultiPhraseQuery查询
		MultiFieldQueryParser queryParser = new MultiFieldQueryParser(fields, LuceneUtils.getAnalyzer());
		Query query = queryParser.parse(keywords);
		TopDocs topDocs = indexSearcher.search(query, 500);
		ScoreDoc[] scoreDocs = topDocs.scoreDocs;

		// 将查询出的数据封装成search对象集合
		for (int i = 0; i < scoreDocs.length; i++) {
			Search search = new Search();
			int doc = scoreDocs[i].doc;
			Document document = indexSearcher.doc(doc);

			search.setId(document.get("id"));

			// 高亮处理
			String title = Highlighter(query, "title", document.get("title"));
			if (title != null) {
				search.setTitle(title);
			} else {
				search.setTitle(document.get("title"));
			}

			String content = Highlighter(query, "content", document.get("content"));
			if (title != null) {
				search.setContent(content);
			} else {
				search.setContent(document.get("content"));
			}

			search.setAuthor(document.get("author"));
			search.setType(document.get("type"));

			String create_time = Highlighter(query, "create_time", document.get("create_time"));
			if (create_time != null) {
				search.setCreate_time(create_time);
			} else {
				search.setCreate_time(document.get("create_time"));
			}

			searchs.add(search);

		}
		return searchs;
	}

	/**
	 * 
	 * @method 控制文字高亮显示,本质上是添加了html标签样式
	 * @author Mr yi
	 * @time 2019年5月23日
	 * @param query
	 * @param field
	 * @param value
	 * @return
	 * @throws Exception
	 */
	private static String Highlighter(Query query, String field, String value) throws Exception {
		QueryScorer queryScorer = new QueryScorer(query);
		// 所要添加的样式
		Formatter formatter = new SimpleHTMLFormatter("<span style='color:red;'>", "</span>");
		// 设置高亮分词器
		Highlighter highlighter = new Highlighter(formatter, queryScorer);
		highlighter.setTextFragmenter(new SimpleFragmenter(100));
		String text = highlighter.getBestFragment(LuceneUtils.getAnalyzer(), field, value);
		return text;
	}

	/**
	 * 
	 * @method 删除文档
	 * @author Mr yi
	 * @time 2019年5月24日
	 * @throws IOException
	 */
	public void deleteDocuments() throws Exception {
		// 获取indexWrite对象
		IndexWriter indexWriter = LuceneUtils.getIndexWriter();
		// 删除title中含有关键词“Spark”的文档
		long count = indexWriter.deleteDocuments(new Term("title", "Spark"));
		// 除此之外IndexWriter还提供了以下方法:
		// DeleteDocuments(Query query):根据Query条件来删除单个或多个Document
		// DeleteDocuments(Query[] queries):根据Query条件来删除单个或多个Document
		// DeleteDocuments(Term term):根据Term来删除单个或多个Document
		// DeleteDocuments(Term[] terms):根据Term来删除单个或多个Document
		// DeleteAll():删除所有的Document

		// 使用IndexWriter进行Document删除操作时,文档并不会立即被删除,而是把这个删除动作缓存起来,当IndexWriter.Commit()或IndexWriter.Close()时,删除操作才会被真正执行。
		indexWriter.commit();
		indexWriter.close();
		System.out.println("删除完成:" + count);
	}
	
	

	/**
	 * @method 更新文档(实际上就是删除后新增一条)
	 * @author Mr yi
	 * @time 2019年5月24日
	 * @param document Document文档对象
	 * @param id 原数据id
	 * @throws Exception
	 */
	public void updateDocumentTest(Document document, String id) throws Exception {
		// 获取indexWrite对象
		IndexWriter indexWriter = LuceneUtils.getIndexWriter();
		//本质上是先删除id为1的索引数据,然后又添加一个更新后的Document文档
		long count = indexWriter.updateDocument(new Term("id", id), document);
		System.out.println("更新文档:" + count);
		indexWriter.close();
	}
	
	/**
	 * 
	 * @method 按词条搜索[TermQuery是最简单、也是最常用的Query。TermQuery可以理解成为“词条搜索”,
	 * 在搜索引擎中最基本的搜索就是在索引中搜索某一词条,而TermQuery就是用来完成这项工作的。
	 * 在Lucene中词条是最基本的搜索单位,从本质上来讲一个词条其实就是一个名/值对。
	 * 只不过这个“名”是字段名,而“值”则表示字段中所包含的某个关键字。
	 * @author Mr yi
	 * @time 2019年5月24日
	 * @param keyword
	 * @throws IOException
	 */
	public void termQuery(String keyword) throws Exception {
		String searchField = "title";
		//这是一个条件查询的api,用于添加条件
		TermQuery query = new TermQuery(new Term(searchField, keyword));
		//执行查询,并打印查询到的记录数
		executeQuery(query);
	}
	
	/**
	 * 
	 * @method 执行查询,并打印查询到的记录数
	 * @author Mr yi
	 * @time 2019年5月24日
	 * @param query
	 * @throws Exception
	 */
	public void executeQuery(Query query) throws Exception {
		IndexSearcher indexSearcher = LuceneUtils.getIndexSearcher();
		TopDocs topDocs = indexSearcher.search(query, 100);
		//打印查询到的记录数
		System.out.println("总共查询到" + topDocs.totalHits + "个文档");
		for (ScoreDoc scoreDoc : topDocs.scoreDocs) {
			//取得对应的文档对象
			Document document = indexSearcher.doc(scoreDoc.doc);
			System.out.println("id:" + document.get("id"));
			System.out.println("title:" + document.get("title"));
			System.out.println("content:" + document.get("content"));
			System.out.println("author:" + document.get("author"));
		}
	}
	
	/**
	 * 
	 * @method 分词打印
	 * @author Mr yi
	 * @time 2019年5月24日
	 * @param analyzer
	 * @param text
	 * @throws IOException
	 */
	public void printAnalyzerDoc(Analyzer analyzer,String text) throws Exception {
		TokenStream tokenStream = analyzer.tokenStream("content", new StringReader(text));
		CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class);
		try {
			tokenStream.reset();
			while (tokenStream.incrementToken()) {
				System.out.println(charTermAttribute.toString());
			}
			tokenStream.end();
		} finally {
			tokenStream.close();
			analyzer.close();
		}
	}
	
	/**
	 * 
	 * @method 多条件查询[BooleanQuery也是实际开发过程中经常使用的一种Query。 它其实是一个组合的Query,在使用时可以把各种Query对象添加进去并标明它们之间的逻辑关系。
	 * BooleanQuery本身来讲是一个布尔子句的容器,它提供了专门的API方法往其中添加子句,标明它们之间的关系,以下代码为BooleanQuery提供的用于添加子句的API接口:]
	 * @author Mr yi
	 * @time 2019年5月24日
	 * @throws Exception
	 */
	public void BooleanQuery() throws Exception {
		String searchField1 = "title";
		String searchField2 = "content";
		Query query1 = new TermQuery(new Term(searchField1, "标题"));
		Query query2 = new TermQuery(new Term(searchField2, "内容"));
		BooleanQuery.Builder builder = new BooleanQuery.Builder();
		// BooleanClause用于表示布尔查询子句关系的类,
		// 包 括:
		// BooleanClause.Occur.MUST,
		// BooleanClause.Occur.MUST_NOT,
		// BooleanClause.Occur.SHOULD。
		// 必须包含,不能包含,可以包含三种.有以下6种组合:
		// 1.MUST和MUST:取得连个查询子句的交集。
		// 2.MUST和MUST_NOT:表示查询结果中不能包含MUST_NOT所对应得查询子句的检索结果。
		// 3.SHOULD与MUST_NOT:连用时,功能同MUST和MUST_NOT。
		// 4.SHOULD与MUST连用时,结果为MUST子句的检索结果,但是SHOULD可影响排序。
		// 5.SHOULD与SHOULD:表示“或”关系,最终检索结果为所有检索子句的并集。
		// 6.MUST_NOT和MUST_NOT:无意义,检索无结果。
		builder.add(query1, BooleanClause.Occur.SHOULD);
		builder.add(query2, BooleanClause.Occur.SHOULD);
		BooleanQuery query = builder.build();

		//执行查询,并打印查询到的记录数
		executeQuery(query);
	}

	/**
	 * 
	 * @method 匹配前缀[ PrefixQuery用于匹配其索引开始以指定的字符串的文档。就是文档中存在xxx%]
	 * @author Mr yi
	 * @time 2019年5月24日
	 * @throws IOException
	 */
	public void prefixQueryTest() throws Exception {
		String searchField = "title";
		Term term = new Term(searchField, "Lucen"); //可以匹配到Lucen%格式
		Query query = new PrefixQuery(term);
		//执行查询,并打印查询到的记录数
		executeQuery(query);
	}
	
	/**
	 * 
	 * @method 短语搜索 
	 * [所谓PhraseQuery,就是通过短语来检索,比如我想查“big car”这个短语,那么如果待匹配的document的指定项里包含了"big car"这个短语,
	 * 这个document就算匹配成功。可如果待匹配的句子里包含的是“big black car”,那么就无法匹配成功了,如果也想让这个匹配,就需要设定slop,
	 * 先给出slop的概念:slop是指两个项的位置之间允许的最大间隔距离
	 * @author Mr yi
	 * @time 2019年5月24日
	 * @throws Exception
	 */
	public void phraseQueryTest() throws Exception {

		String searchField = "content";
		String query1 = "apache";
		String query2 = "spark";
		PhraseQuery.Builder builder = new PhraseQuery.Builder();
		builder.add(new Term(searchField, query1));
		builder.add(new Term(searchField, query2));
		builder.setSlop(0);
		PhraseQuery phraseQuery = builder.build();
		//执行查询,并打印查询到的记录数
		executeQuery(phraseQuery);
	}
	
	/**
	 * 
	 * @method 相近词语搜索
	 * [FuzzyQuery是一种模糊查询,它可以简单地识别两个相近的词语。]
	 * @author Mr yi
	 * @time 2019年5月24日
	 * @throws Exception
	 */
	public void fuzzyQueryTest() throws Exception {
		String searchField = "content";
		Term t = new Term(searchField, "大规模");
		Query query = new FuzzyQuery(t);
		//执行查询,并打印查询到的记录数
		executeQuery(query);
	}
	
	/**
	 * 
	 * @method 通配符搜索
	 * [ Lucene也提供了通配符的查询,这就是WildcardQuery。通配符“?”代表1个字符,而“*”则代表0至多个字符。]
	 * @author Mr yi
	 * @time 2019年5月24日
	 * @throws Exception
	 */
	public void wildcardQueryTest() throws Exception {
		String searchField = "content";
		Term term = new Term(searchField, "大*规模");
		Query query = new WildcardQuery(term);
		//执行查询,并打印查询到的记录数
		executeQuery(query);
	}

	/**
	 * 
	 * @method 分词查询
	 * @author Mr yi
	 * @time 2019年5月24日
	 * @throws Exception
	 */
	public void queryParser() throws Exception {
		Analyzer analyzer =LuceneUtils.getAnalyzer();
		String searchField = "content";
		//指定搜索字段和分析器
		QueryParser parser = new QueryParser(searchField, analyzer);
		//用户输入内容
		Query query = parser.parse("计算引擎");
		//执行查询,并打印查询到的记录数
		executeQuery(query);
	}
	
	/**
	 * 
	 * @method 多个 Field 分词查询
	 * @author Mr yi
	 * @time 2019年5月24日
	 * @throws Exception
	 */
	public void multiFieldQueryParser() throws Exception  {
		Analyzer analyzer =LuceneUtils.getAnalyzer();
		String[] filedStr = new String[]{"title", "content"};
		//指定搜索字段和分析器
		QueryParser queryParser = new MultiFieldQueryParser(filedStr, analyzer);
		//用户输入内容
		Query query = queryParser.parse("Spark");
		//执行查询,并打印查询到的记录数
		executeQuery(query);
	}
	
	/**
	 * 
	 * @method 中文分词器[选择不同analyzer分词器会有不同结果]
	 * @author Mr yi
	 * @time 2019年5月24日
	 * @throws Exception
	 */
	public void AnalyzerTest() throws Exception {
		Analyzer analyzer =LuceneUtils.getAnalyzer();
		String text = "Apache Spark 是专为大规模数据处理而设计的快速通用的计算引擎";
		printAnalyzerDoc(analyzer, text);
		 
	}
}

三、根据关键字搜索

/**
	 * 
	 * @method 根据关键字查找索引,返回符合关键字的全部数据
	 * @author Mr yi
	 * @time 2019年5月27日
	 * @param model
	 * @param keyword
	 * @return
	 * @throws Exception
	 */
	@RequestMapping("/search")
	public String search(Model model,@RequestParam("keyword") String keyword) throws Exception{
	    List<Search> list=SearchUtils.findIndex(keyword);
	    System.out.println(list);
	    model.addAttribute("list", list);
		return "search/search_list";

猜你喜欢

转载自blog.csdn.net/qq_29914837/article/details/90516786