最近利用晚上下班还有周末的时间自己捣腾的封装了一个我自己的全文检索引擎(基于lucene和solr).现在将大概的思路给写出来,分享下:
1.首先是索引对象,也可以说是查询的VO对象.封装了几个常用字段(如:主键,所属者ID,所属者姓名,进入详情页面的link,创建时间等),其他各个模块的字段(如:标题,内容,邮箱等)
SearchBean.java
字段的代码如下:
/********以下 共有字段***********/ /** * 检索的内容 */ protected String keyword; /** * 拥有者ID */ protected String owerId; /** * 拥有者name */ protected String owerName; /** * 检索对象的唯一标识位的值 */ protected String id; /** * 检索出对象后进入详情页面的链接 */ protected String link; /** * 创建时间 */ protected String createDate; /** * index类型 */ protected String indexType; //setter,getter方法省略 /********以上 共有字段***********/ /*************以下 其他字段************/ /** * 需要检索出来的字段及其值的对应map */ private Map<String, String> searchValues; /** * 值对象 */ private Object object; /** * 获取检索出来的doIndexFields字段的值 * * @return */ public Map<String, String> getSearchValues() { return searchValues; } /** * 设置检索出来的doIndexFields字段的值 * * @param searchValues */ public void setSearchValues(Map<String, String> searchValues) { this.searchValues = searchValues; } /********************以上 其他字段*******************/<span></span>
抽象方法代码如下:
/*****************以下 抽象方法******************/ /** * 返回需要进行检索的字段 * * @return */ public abstract String[] getDoSearchFields(); /** * 进行索引的字段 * * @return */ public abstract String[] getDoIndexFields(); /** * 初始化searchBean中的公共字段(每个对象都必须创建的索引字段) * @throws Exception */ public abstract void initPublicFields() throws Exception; /** * 返回索引类型 * * @return */ public abstract String getIndexType(); /*****************以上 抽象方法********************/
共有的方法:
/*******************以下 公共方法**********************/ /** * 获取需要创建索引字段的键值对map * * @return */ public Map<String, String> getIndexFieldValues() { if(this.object == null){ logger.warn("given object is null!"); return Collections.emptyMap(); } String[] doIndexFields = this.getDoIndexFields(); if(doIndexFields == null || doIndexFields.length < 1){ logger.debug("given no doIndexFields!"); return Collections.emptyMap(); } Map<String, String> extInfo = new HashMap<String, String>(); for(String f : doIndexFields){ String value = getValue(f, object); extInfo.put(f, value); } return extInfo; } /** * 获取一个对象中的某个字段的值,结果转化成string类型 * * @param field 字段名称 * @param obj 对象 * @return */ private String getValue(String field, Object obj){ if(StringUtils.isEmpty(field)){ logger.warn("field is empty!"); return StringUtils.EMPTY; } String result = StringUtils.EMPTY; try { Object value = ObjectUtils.getFieldValue(object, field); if (value == null) result = StringUtils.EMPTY; else if (value instanceof String) result = (String) value; else if (value instanceof Collections || value instanceof Map) result = ToStringBuilder.reflectionToString(object); else if (value instanceof Date) result = DateUtils.formatDate((Date) value); else result = value.toString(); } catch (IllegalAccessException e) { logger.error("can not find a value for field '{}' in object class '{}'!", field, object.getClass()); } return result; } /** * you must use this method when you create the index, set what object you will to be created its index! * * @param object the object which you will want to be create index */ public void setObject(Object object){ this.object = object; } /** * get what object you want to be created index! * * @return */ public Object getObject(){ return this.object; } /***************以上 公共方法*************/
2.现在有很多开源或者闭源的索引引擎可以用在项目上使用,所以我写了一个接口和一个抽取了一些公共方法的抽象类,只需要将你选择的搜索引擎的具体创建索引,检索等功能的实现代码写在一个继承上面这个抽象类的子类中,就可以随意的切换使用的目标引擎.贴上接口和抽象类
SearchEngine.java
package com.message.base.search.engine; import com.message.base.pagination.PaginationSupport; import com.message.base.search.SearchBean; import java.util.List; /** * 索引引擎实现构建索引.删除索引.更新索引.检索等操作. * * @author sunhao(sunhao.java@gmail.com) * @version V1.0 * @createTime 13-5-5 上午1:38 */ public interface SearchEngine { /** * 创建索引(考虑线程安全) * * @param searchBeans 对象 * @throws Exception */ public void doIndex(List<SearchBean> searchBeans) throws Exception; /** * 删除索引 * * @param bean 对象 * @throws Exception */ public void deleteIndex(SearchBean bean) throws Exception; /** * 删除索引(删除多个) * * @param beans 对象 * @throws Exception */ public void deleteIndexs(List<SearchBean> beans) throws Exception; /** * 进行检索 * * @param bean 检索对象(一般只需要放入值keyword,即用来检索的关键字) * @param isHighlighter 是否高亮 * @param start 开始值 * @param num 偏移量 * @return * @throws Exception */ public PaginationSupport doSearch(SearchBean bean, boolean isHighlighter, int start, int num) throws Exception; /** * 进行多个检索对象的检索 * * @param beans 多个检索对象(一般只需要放入值keyword,即用来检索的关键字) * @param isHighlighter 是否高亮 * @param start 开始值 * @param num 偏移量 * @return * @throws Exception */ public PaginationSupport doSearch(List<SearchBean> beans, boolean isHighlighter, int start, int num) throws Exception; /** * 删除某个类型的所有索引(考虑线程安全) * * @param clazz 索引类型 * @throws Exception */ public void deleteIndexsByIndexType(Class<? extends SearchBean> clazz) throws Exception; /** * 删除某个类型的所有索引(考虑线程安全) * * @param indexType 索引类型 * @throws Exception */ public void deleteIndexsByIndexType(String indexType) throws Exception; /** * 删除所有的索引 * * @throws Exception */ public void deleteAllIndexs() throws Exception; /** * 更新索引 * * @param searchBean 需要更新的bean * @throws Exception */ public void updateIndex(SearchBean searchBean) throws Exception; /** * 批量更新索引 * * @param searchBeans 需要更新的beans * @throws Exception */ public void updateIndexs(List<SearchBean> searchBeans) throws Exception; }
AbstractSearchEngine.java
package com.message.base.search.engine; import com.message.base.pagination.PaginationSupport; import com.message.base.pagination.PaginationUtils; import com.message.base.search.SearchBean; import com.message.base.utils.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Collections; /** * 搜索引擎的公用方法. * * @author sunhao(sunhao.java@gmail.com) * @version V1.0 * @createTime 13-5-8 下午10:53 */ public abstract class AbstractSearchEngine implements SearchEngine { private static final Logger logger = LoggerFactory.getLogger(AbstractSearchEngine.class); /** * 进行高亮处理时,html片段的前缀 */ private String htmlPrefix = "<p>"; /** * 进行高亮处理时,html片段的后缀 */ private String htmlSuffix = "</p>"; public String getHtmlPrefix() { return htmlPrefix; } public void setHtmlPrefix(String htmlPrefix) { this.htmlPrefix = htmlPrefix; } public String getHtmlSuffix() { return htmlSuffix; } public void setHtmlSuffix(String htmlSuffix) { this.htmlSuffix = htmlSuffix; } public PaginationSupport doSearch(SearchBean bean, boolean isHighlighter, int start, int num) throws Exception { if(bean == null){ logger.debug("given search bean is empty!"); return PaginationUtils.getNullPagination(); } return doSearch(Collections.singletonList(bean), isHighlighter, start, num); } /** * 获取index类型 * * @param bean * @return */ public String getIndexType(SearchBean bean){ return StringUtils.isNotEmpty(bean.getIndexType()) ? bean.getIndexType() : bean.getClass().getSimpleName(); } }
3.开始谈谈lucene
贴上代码先:
LuceneSearchEngine.java
package com.message.base.search.engine; import com.message.base.pagination.PaginationSupport; import com.message.base.pagination.PaginationUtils; import com.message.base.search.SearchBean; import com.message.base.search.SearchInitException; import com.message.base.utils.StringUtils; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.SimpleAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.Term; import org.apache.lucene.queryParser.MultiFieldQueryParser; import org.apache.lucene.search.BooleanClause; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Query; import org.apache.lucene.search.ScoreDoc; import org.apache.lucene.search.highlight.Highlighter; import org.apache.lucene.search.highlight.QueryScorer; import org.apache.lucene.search.highlight.SimpleHTMLFormatter; import org.apache.lucene.store.Directory; import org.apache.lucene.store.FSDirectory; import org.apache.lucene.util.Version; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeanUtils; import java.io.File; import java.io.IOException; import java.util.*; /** * 基于lucene实现的索引引擎. * * @author sunhao(sunhao.java@gmail.com) * @version V1.0 * @createTime 13-5-5 上午10:38 */ public class LuceneSearchEngine extends AbstractSearchEngine { private static final Logger logger = LoggerFactory.getLogger(LuceneSearchEngine.class); /** * 索引存放路径 */ private String indexPath; /** * 分词器 */ private Analyzer analyzer = new SimpleAnalyzer(); public synchronized void doIndex(List<SearchBean> searchBeans) throws Exception { this.createOrUpdateIndex(searchBeans, true); } public synchronized void deleteIndex(SearchBean bean) throws Exception { if(bean == null){ logger.warn("Get search bean is empty!"); return; } String id = bean.getId(); if(StringUtils.isEmpty(id)){ logger.warn("get id and id value from bean is empty!"); return; } String indexType = getIndexType(bean); Directory indexDir = this.getIndexDir(indexType); IndexWriter writer = this.getWriter(indexDir); writer.deleteDocuments(new Term("pkId", id)); writer.commit(); this.destroy(writer); } public synchronized void deleteIndexs(List<SearchBean> beans) throws Exception { if(beans == null){ logger.warn("Get beans is empty!"); return; } for(SearchBean bean : beans){ this.deleteIndex(bean); } } public PaginationSupport doSearch(List<SearchBean> beans, boolean isHighlighter, int start, int num) throws Exception { if(beans == null || beans.isEmpty()){ logger.debug("given search beans is empty!"); return PaginationUtils.getNullPagination(); } List queryResults = new ArrayList(); int count = 0; for(SearchBean bean : beans){ String indexType = getIndexType(bean); IndexReader reader = IndexReader.open(this.getIndexDir(indexType)); List<String> fieldNames = new ArrayList<String>(); //查询的字段名 List<String> queryValue = new ArrayList<String>(); //待查询字段的值 List<BooleanClause.Occur> flags = new ArrayList<BooleanClause.Occur>(); //要进行检索的字段 String[] doSearchFields = bean.getDoSearchFields(); if(doSearchFields == null || doSearchFields.length == 0) return PaginationUtils.getNullPagination(); //默认字段 if(StringUtils.isNotEmpty(bean.getKeyword())){ for(String field : doSearchFields){ fieldNames.add(field); queryValue.add(bean.getKeyword()); flags.add(BooleanClause.Occur.SHOULD); } } Query query = MultiFieldQueryParser.parse(Version.LUCENE_CURRENT, queryValue.toArray(new String[]{}), fieldNames.toArray(new String[]{}), flags.toArray(new BooleanClause.Occur[]{}), analyzer); logger.debug("make query string is '{}'!", query.toString()); IndexSearcher searcher = new IndexSearcher(reader); ScoreDoc[] scoreDocs = searcher.search(query, 1000000).scoreDocs; //查询起始记录位置 int begin = (start == -1 && num == -1) ? 0 : start; //查询终止记录位置 int end = (start == -1 && num == -1) ? scoreDocs.length : Math.min(begin + num, scoreDocs.length); //高亮处理 Highlighter highlighter = null; if(isHighlighter){ SimpleHTMLFormatter formatter = new SimpleHTMLFormatter(this.getHtmlPrefix(), this.getHtmlSuffix()); highlighter = new Highlighter(formatter, new QueryScorer(query)); } List<SearchBean> results = new ArrayList<SearchBean>(); for (int i = begin; i < end; i++) { SearchBean result = BeanUtils.instantiate(bean.getClass()); int docID = scoreDocs[i].doc; Document hitDoc = searcher.doc(docID); result.setId(hitDoc.get("pkId")); result.setLink(hitDoc.get("link")); result.setOwerId(hitDoc.get("owerId")); result.setOwerName(hitDoc.get("owerName")); result.setCreateDate(hitDoc.get("createDate")); result.setIndexType(indexType); String keyword = StringUtils.EMPTY; if(isHighlighter && highlighter != null) keyword = highlighter.getBestFragment(analyzer, "keyword", hitDoc.get("keyword")); if(StringUtils.isEmpty(keyword)) keyword = hitDoc.get("keyword"); result.setKeyword(keyword); Map<String, String> extendValues = new HashMap<String, String>(); for(String field : doSearchFields){ String value = hitDoc.get(field); if(isHighlighter && highlighter != null) value = highlighter.getBestFragment(analyzer, field, hitDoc.get(field)); if(StringUtils.isEmpty(value)) value = hitDoc.get(field); extendValues.put(field, value); } result.setSearchValues(extendValues); results.add(result); } queryResults.addAll(results); count += scoreDocs.length; searcher.close(); reader.close(); } PaginationSupport paginationSupport = PaginationUtils.makePagination(queryResults, count, num, start); return paginationSupport; } public synchronized void deleteIndexsByIndexType(Class<? extends SearchBean> clazz) throws Exception { String indexType = getIndexType(BeanUtils.instantiate(clazz)); this.deleteIndexsByIndexType(indexType); } public synchronized void deleteIndexsByIndexType(String indexType) throws Exception { //传入readOnly的参数,默认是只读的 IndexReader reader = IndexReader.open(this.getIndexDir(indexType), false); int result = reader.deleteDocuments(new Term("indexType", indexType)); reader.close(); logger.debug("the rows of delete index is '{}'! index type is '{}'!", result, indexType); } public synchronized void deleteAllIndexs() throws Exception { File indexFolder = new File(this.indexPath); if(indexFolder == null || !indexFolder.isDirectory()){ //不存在或者不是文件夹 logger.debug("indexPath is not a folder! indexPath: '{}'!", indexPath); return; } File[] children = indexFolder.listFiles(); for(File child : children){ if(child == null || !child.isDirectory()) continue; String indexType = child.getName(); logger.debug("Get indexType is '{}'!", indexType); this.deleteIndexsByIndexType(indexType); } } public void updateIndex(SearchBean searchBean) throws Exception { this.updateIndexs(Collections.singletonList(searchBean)); } public void updateIndexs(List<SearchBean> searchBeans) throws Exception { this.createOrUpdateIndex(searchBeans, false); } /** * 创建或者更新索引 * * @param searchBeans 需要创建或者更新的对象 * @param isCreate 是否是创建索引;true创建索引,false更新索引 * @throws Exception */ private synchronized void createOrUpdateIndex(List<SearchBean> searchBeans, boolean isCreate) throws Exception { if(searchBeans == null || searchBeans.isEmpty()){ logger.debug("do no index!"); return; } Directory indexDir = null; IndexWriter writer = null; for(Iterator<SearchBean> it = searchBeans.iterator(); it.hasNext(); ){ SearchBean sb = it.next(); String indexType = getIndexType(sb); if(sb == null){ logger.debug("give SearchBean is null!"); return; } boolean anotherSearchBean = indexDir != null && !indexType.equals(((FSDirectory) indexDir).getFile().getName()); if(indexDir == null || anotherSearchBean){ indexDir = this.getIndexDir(indexType); } if(writer == null || anotherSearchBean){ this.destroy(writer); writer = this.getWriter(indexDir); } Document doc = new Document(); //初始化一些字段 sb.initPublicFields(); String id = sb.getId(); //主键的索引,不作为搜索字段,并且也不进行分词 Field idField = new Field("pkId", id, Field.Store.YES, Field.Index.NOT_ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS); doc.add(idField); logger.debug("create id index for '{}', value is '{}'! index is '{}'!", new Object[]{"pkId", id, idField}); String owerId = sb.getOwerId(); if(StringUtils.isEmpty(owerId)){ throw new SearchInitException("you must give a owerId"); } Field owerId_ = new Field("owerId", owerId, Field.Store.YES, Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS); doc.add(owerId_); String owerName = sb.getOwerName(); if(StringUtils.isEmpty(owerName)){ throw new SearchInitException("you must give a owerName"); } Field owerName_ = new Field("owerName", owerName, Field.Store.YES, Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS); doc.add(owerName_); String link = sb.getLink(); if(StringUtils.isEmpty(link)){ throw new SearchInitException("you must give a link"); } Field link_ = new Field("link", link, Field.Store.YES, Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS); doc.add(link_); String keyword = sb.getKeyword(); if(StringUtils.isEmpty(keyword)){ throw new SearchInitException("you must give a keyword"); } Field keyword_ = new Field("keyword", keyword, Field.Store.YES, Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS); doc.add(keyword_); String createDate = sb.getCreateDate(); if(StringUtils.isEmpty(createDate)){ throw new SearchInitException("you must give a createDate"); } Field createDate_ = new Field("createDate", createDate, Field.Store.YES, Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS); doc.add(createDate_); //索引类型字段 Field indexType_ = new Field("indexType", indexType, Field.Store.YES, Field.Index.NOT_ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS); doc.add(indexType_); //进行索引的字段 String[] doIndexFields = sb.getDoIndexFields(); Map<String, String> indexFieldValues = sb.getIndexFieldValues(); if(doIndexFields != null && doIndexFields.length > 0){ for(String field : doIndexFields){ Field extInfoField = new Field(field, indexFieldValues.get(field), Field.Store.YES, Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS); doc.add(extInfoField); } } if(isCreate) writer.addDocument(doc); else writer.updateDocument(new Term("pkId", sb.getId()), doc); writer.optimize(); } this.destroy(writer); logger.debug("create or update index success!"); } public Directory getIndexDir(String suffix) throws Exception { return FSDirectory.open(new File(indexPath + File.separator + suffix)); } public IndexWriter getWriter(Directory indexDir) throws IOException { return new IndexWriter(indexDir, analyzer, IndexWriter.MaxFieldLength.UNLIMITED); } public void destroy(IndexWriter writer) throws Exception { if(writer != null) writer.close(); } public void setIndexPath(String indexPath) { this.indexPath = indexPath; } public void setAnalyzer(Analyzer analyzer) { this.analyzer = analyzer; } }
关于如何使用lucene这里我就不再重复了,网上一大堆这方面的资料,有什么不懂得可以谷歌一下.下面谈谈我的一些想法,有不对的,尽管拍砖,来吧:
....
也没啥好说的,等想到再补充吧,就是觉得有一点比较操蛋,窝心:
FSDirectory.open(new File("D:\index\xxx"/**一个不存在的目录,或者是一个不是索引的目录**/));
使用上面一段取到索引Directory的时候,如果目录不存在会报错.可以有人认为这没什么,就是应该,我封装的这代码里面,确实对这玩意有要求的.
上面的SearchBean.java中有一个字段叫indexType,当没有指定的时候,默认为类名,如MessageSerarchBean,如果我没有对Message进行创建索引操作,在检索的时候就报错了.我得想想用什么方法给解决掉.
下一篇:我封装的全文检索之lucene篇
相关推荐
在"lucene+api搜索引擎引工具源码"项目中,开发者可能已经封装了 Lucene 的核心功能,使其更易于在 C#.NET 平台上使用。源码可能会包含以下关键组件和概念: 1. **索引创建**:Lucene 允许开发者将文本数据(如文档...
**搜索引擎Lucene详解** 搜索引擎Lucene是Apache软件基金会下的一个开放源代码全文检索库,它提供了高效的、可扩展的文本搜索功能。Lucene的核心功能包括文档索引、搜索以及相关的排序算法,使得开发者能够轻松地在...
Java全职搜索引擎Lucene是一个强大的开源工具,用于创建高效的全文索引和搜索功能。它由Apache软件基金会维护,被广泛应用于各种网站和应用程序中,以提供快速、精准的站内搜索体验。本教程将深入讲解如何使用Lucene...
Compass是一个开源项目,它将Lucene的功能封装起来,提供了一套面向对象的API,简化了在Java应用中集成全文搜索引擎的过程。Compass不仅支持对关系数据库中的数据进行索引,还能与ORM框架如Hibernate、JPA等无缝集成...
**全文搜索引擎Lucene入门** 全文搜索引擎Lucene是Apache软件基金会的一个开放源代码项目,它为Java开发者提供了一个高性能、可扩展的信息检索库。Lucene以其强大的文本搜索功能和高效的索引能力,在各种需要全文...
在"Lucene全文搜索_LuceneJava全文搜索_"这个主题中,我们将深入探讨Lucene如何在Java环境中实现高效的全文搜索引擎。首先,Lucene的核心概念包括文档(Document)、字段(Field)、索引(Index)和搜索(Search)。...
**Lucene 搜索引擎开发包详解** Lucene 是一个开源的全文检索库,由 Apache 软件基金会维护。它提供了高性能、可扩展的搜索功能,广泛应用于网站、应用程序及大数据分析等领域。作为 Java 语言编写的核心库,Lucene...
Examine 允许您轻松地索引和搜索数据,并封装了 Lucene.Net 索引/搜索引擎。Lucene速度非常快,即使在非常大量的数据上也可以进行非常快速的搜索。检查是非常可扩展的,允许您配置任意数量的索引,并且每个索引都...
Java基于Lucene的GIS搜索引擎是一种高效、便捷的工具,它专为处理SHP(Shapefile)文件而设计,这是地理信息系统(GIS)中常见的矢量数据格式。通过使用Lucene,这种搜索引擎能够对SHP文件进行索引,极大地提升了...
该项目为基于Lucene封装的天狗全文搜索数据库引擎源码,采用Java语言编写,并涉及CSS、JavaScript、Shell和HTML等多种语言。项目包含303个文件,其中Java源文件64个,JAR包文件41个,PNG图片文件135个,CSS文件9个,...
Lucene是一个高性能、全文检索库,它提供了强大的文本分析和索引功能,广泛应用于搜索引擎开发和其他需要高效文本处理的场景。本篇文章主要面向初学者,通过实例详细解释如何使用Lucene进行文档搜索。 首先,我们...
《深入理解Lucene.NET:构建高效全文搜索引擎》 Lucene.NET是一个开源的、高性能的全文检索库,它是Apache Lucene项目在.NET平台上的实现。这个强大的工具为.NET开发者提供了构建高效全文搜索引擎的能力,广泛应用...
Apache Lucene是一个高性能、全文本搜索库,被广泛用于构建自定义的搜索引擎。它提供了丰富的搜索功能,包括分词、索引、查询解析、排序等。在这个项目中,我们将深入探讨如何使用Java和Lucene来创建一个简单的搜索...
Apache Lucene 是一个开源的全文搜索引擎库,由Java编写,为开发者提供了强大的文本检索功能。本篇文章将深入探讨Lucene的核心概念、工作原理以及如何通过一个Demo来实践它的使用。 Lucene的主要功能包括索引构建、...
Elasticsearch是一个开源的分布式搜索引擎,具有全文搜索、实时性、高可用性和可扩展性等特点。它基于Lucene库,提供了RESTful API接口,方便各种编程语言进行调用。 在C#中与Elasticsearch交互,我们通常会使用...
该毕业设计项目主要基于DOT.NET框架,利用Ajax技术和Lucene库构建了一个搜索引擎,旨在提供高效、实时的搜索体验。在本文中,我们将深入探讨这个设计的各个方面,包括DOT.NET框架的基础,Ajax技术的应用,以及Lucene...