lucene是一个全文检索引擎(非分布式),使用java编写并且可以很方便的嵌入到各种系统中以实现全文检索功能,其通过对字符串进行分词,然后针对分词结果分别建立索引,存入内存或者磁盘,以提供搜索服务,这个功能用起来就像是一个单机版的google,模糊匹配的结果会被展示给用户。应用lucene较为出名的就包括了eclipse的帮助系统。
很多时候搜索这个概念会含糊不清,通常意义上对于一个网站的搜索功能是针对于从数据库中捞取数据并通过freemarker或者velocity渲染模板后展示出来,然后搜索的另一个含义,是针对于文件系统而言,将文件的内容进行搜索并读取文件展示出来。那么对于lucene而言,显然更大的用于之地在于后者,虽然前者的功能也是能够实现。
我们可以想象google的服务器不断的定时dump全球Internet的网页,然后放入自己的存储区域,通过检索评级等一系列的复杂算法,建立针对于关键字的索引,当用户输入关键字进行查询的时候,她会根据建立的索引以及评级等信息进行相似度的判断并展现出查询结果。当然具体的过程会远远比这个复杂。这里要说的是建立搜索的这个概念,从简单的数据库查询的思想出脱离出来,建立一个全文检索的概念。
索引可以理解成现实中书籍的目录,通过这个目录可以很快的定位到具体的页数,拿到想要的结果。这个和数据库的索引是一样的功能,都是为了提高效率,然后数据库却无法实现一个高效的模糊匹配,通常而言的%like%语句,是一条一条的数据比对类似于书籍的翻页过程,这样的效率是极其低下而且对于数据库系统而言是很大的性能耗费,尤其是当有多个关键字的时候,多个%like%的查询条件足以让DBA疯掉。
所以对于模糊匹配的查询而言,高效而准确是一个关键的因素。而lucene的优势就是通过对文本进行分词,通过分词得到一系列的关键字以用于建立索引,同时使用自己默认的socre相关度算法进行排序,通常而言如果对于搜索结果有自己特殊的排序要求,可以在使用lucene建立索引之前,先将数据排好序,可以防止lucene默认的排序之外再次进行排序操作。
对于原理性的知识,可以参看网络上的一些文章,google一下看到很多写的都不错,个人不是很擅长写这种原理性的东西,所以下面做一个小实验,来熟悉一下具体的lucene step by step
我们的需求是针对于一个网站的帮助系统开发一个搜索功能,用户输入自己希望搜索的问题或者关键字,我们通过lucene进行搜索并展示结果给用户。
就是这样简单的一个需求,假设我们的帮助系统的文档是存放在数据库中的,这个数据量在百这个级别,很小的一个数据量。我们先模拟一个小数据量来实现这个功能,如果数据量是百万级那需要进行分布式的索引建立和搜索,那将涉及到其他额外的很多条件和问题解决方案。
我们希望通过这个系统的开发,不但能解决当前的帮助系统的问题,还可以一定程度上解决一些数据量较小的其他搜索问题,所以是势必需要我们去开发一个通用的中间件,并且通过一定的控制手段来注入特定的业务。
要建立索引,我们需要知道面对的数据对象是谁?哪张表?在哪些字段上建立索引?这几个是关键因素,当然还包括了索引文件存放位置等小问题暂且掠过。这几个关键的因素我们需要注入到我们开发的中间件中,通过配置注入的方式隔离具体的业务代码交织。
怎么做呢?如何去从数据库中获取数据并建立索引呢?索引文件以多大的频率更新?
看看代码:
- package com.normandy.position.common;
- import java.io.IOException;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.Collection;
- import java.util.HashMap;
- import java.util.Iterator;
- import java.util.List;
- import java.util.Map;
- import org.apache.commons.lang.StringUtils;
- import org.apache.log4j.Logger;
- import org.apache.lucene.analysis.Analyzer;
- import org.apache.lucene.analysis.cjk.CJKAnalyzer;
- import org.apache.lucene.document.Document;
- import org.apache.lucene.document.Field;
- import org.apache.lucene.index.CorruptIndexException;
- import org.apache.lucene.index.IndexReader;
- import org.apache.lucene.index.IndexReader.FieldOption;
- import org.apache.lucene.index.IndexWriter;
- import org.apache.lucene.index.Term;
- import org.apache.lucene.queryParser.ParseException;
- import org.apache.lucene.queryParser.QueryParser;
- import org.apache.lucene.search.Hits;
- import org.apache.lucene.search.IndexSearcher;
- import org.apache.lucene.search.Query;
- import org.apache.lucene.store.LockObtainFailedException;
- import org.springframework.jdbc.core.JdbcTemplate;
- /**
- * 使用lucene进行搜索服务
- * <p>
- * 适用于数据量不大的单机搜索服务,对于数据量较大的搜索,建议使用分布式搜索
- * </p>
- *
- * @author quzishen
- * @version 1.0
- */
- public class LuceneSearcher implements Runnable {
- protected final Logger logger = Logger.getLogger(LuceneSearcher.class);
- /** ~~~ 类名 */
- private String className;
- /** ~~~ 需要建立索引的域列表字符串,以“,”隔开 */
- private String fieldsStr;
- /** ~~~ 默认的索引存放目录 */
- private String defaultIndexDir = "c:/index/";
- /** ~~~ 配置中需要索引字段的默认分隔符 */
- private static final String DEFAULT_KEY_FIELD_SPLIT = ",";
- /** ~~~ 默认的标记符名称,如果fieldsStr中含有主键,则使用主键名称*/
- private static final String DEFAULT_ID = "id";
- /** ~~~ 是否每次重新建立索引 */
- private boolean IS_REBUILD_INDEX = true;
- /** ~~~ 默认的建立索引的最大数目 */
- private int DEFAULT_MAX_INDEX_NUMS = Integer.MAX_VALUE;
- /** ~~~ 特别针对于匿名内部类提供的操作jdbc模板 */
- private JdbcTemplate jdbcTemplate;
- /**
- * 建立索引,初始化操作
- *
- * @throws RuntimeException
- */
- public void initIndex() throws RuntimeException {
- if (StringUtils.isBlank(fieldsStr) || StringUtils.isBlank(className)) {
- throw new RuntimeException("can not build the index by null value of field and className.");
- }
- long beginTime = System.currentTimeMillis();
- if (logger.isInfoEnabled()) {
- logger.info("begin to build the lucene index...");
- }
- Analyzer analyzer = new CJKAnalyzer();
- try {
- // 获取需要建立索引的域
- List<String> fieldList = getKeyWordsList();
- IndexWriter indexWriter = new IndexWriter(defaultIndexDir, analyzer, IS_REBUILD_INDEX);
- // 控制写入一个新的segment前在内存中保存的最大的document数目
- indexWriter.setMaxBufferedDocs(500);
- // 控制多个segment合并的频率
- indexWriter.setMaxMergeDocs(100);
- buildIndex(fieldList,indexWriter);
- indexWriter.optimize();
- indexWriter.close();
- long endTime = System.currentTimeMillis();
- if (logger.isInfoEnabled()) {
- logger.info("end to build the lucene index...,use time :"
- + (endTime - beginTime) + "ms.");
- }
- } catch (IOException e) {
- logger.error("create index failed!check the authentation!", e);
- throw new RuntimeException("create index failed!check the authentation!", e);
- } catch (ClassNotFoundException e) {
- logger.error("class not found : " + className, e);
- throw new RuntimeException("class not found : " + className, e);
- }
- }
- /**
- * 重新建立索引
- */
- public void run() {
- if(logger.isDebugEnabled()){
- logger.debug("rebuild the index for lucene start...");
- }
- long begin = System.currentTimeMillis();
- removeAllIndex();
- initIndex();
- long end = System.currentTimeMillis();
- if(logger.isDebugEnabled()){
- logger.debug("rebuild the index for lucene end..."+(end - begin)+"ms.");
- }
- }
- /**
- * 重新建立索引
- * @throws RuntimeException
- */
- public void refreshIndex() throws RuntimeException {
- new Thread(this).start();
- }
- /**
- * 删除所有的索引,将根据主键一次性全部删除
- * @throws RuntimeException
- */
- @SuppressWarnings("unchecked")
- public void removeAllIndex() throws RuntimeException {
- try {
- // reader
- IndexReader indexReader = IndexReader.open(defaultIndexDir);
- Analyzer analyzer = new CJKAnalyzer();
- IndexWriter indexWriter = new IndexWriter(defaultIndexDir,analyzer);
- // 获取所有的索引名称集合
- Collection<String> indexs = indexReader.getFieldNames(FieldOption.INDEXED);
- // 检查是否包含主键
- String keyName = getKeyName();
- if(!indexs.contains(keyName)){
- return;
- }
- // 遍历并删除
- int maxDocNum = indexReader.maxDoc();
- for(int k = 0;k < maxDocNum ;k++){
- Document document = indexReader.document(k);
- String value = document.get(keyName);
- Term term = new Term(keyName,value);
- indexWriter.deleteDocuments(term);
- if(logger.isDebugEnabled()){
- logger.debug("delete the index of ["+keyName+","+value+"]");
- }
- logger.error("delete the index of ["+keyName+","+value+"]");
- }
- indexWriter.optimize();
- indexWriter.close();
- indexReader.flush();
- indexReader.close();
- } catch (CorruptIndexException e) {
- logger.error("create index failed!", e);
- throw new RuntimeException("create index failed!",e);
- } catch (LockObtainFailedException e) {
- logger.error("create index failed!", e);
- throw new RuntimeException("create index failed!",e);
- } catch (IOException e) {
- logger.error("create index failed!check the authentation!", e);
- throw new RuntimeException("create index failed!",e);
- } catch (ClassNotFoundException e) {
- logger.error("class not found!", e);
- throw new RuntimeException("create index failed!",e);
- }
- }
- /**
- * 从数据库中取出数据,建立索引用于全文检索
- * @param fieldList 建立索引的字段列表
- * @param indexWriter
- * @throws RuntimeException
- */
- @SuppressWarnings("unchecked")
- private void buildIndex(List<String> fieldList,IndexWriter indexWriter) throws RuntimeException{
- try{
- // 获取类型
- Class<? extends Object> objectClass = Class.forName(className);
- // 匿名内部类
- AbstractBaseDAO abstractBaseDAO = new AbstractBaseDAO() {
- };
- abstractBaseDAO.setJdbcTemplate(jdbcTemplate);
- // 获取第一页
- Paginal<? extends Object> paginal = abstractBaseDAO.queryFieldsListForPaging(objectClass, null, fieldList, 1, 1000);
- // 修正分页总数,如果搜索结果总数超过最大值,则使用最大值
- int totalCount = paginal.getTotalCount();
- totalCount = totalCount > DEFAULT_MAX_INDEX_NUMS ? DEFAULT_MAX_INDEX_NUMS : totalCount;
- paginal.setTotalCount(totalCount);
- // 需要分页的数目
- int pageNum = paginal.getPageNum();
- // 循环从数据库分页读取数据
- for (int i = 0; i < pageNum; i++) {
- // 查询结果列表
- List<? extends Object> resultList = paginal.getResultList();
- Iterator<? extends Object> resultIndex = resultList.iterator();
- while (resultIndex.hasNext()) {
- // 每一个新建document,防止field重名覆盖
- Document document = new Document();
- Object object = resultIndex.next();
- Iterator<String> fieldIndex = fieldList.iterator();
- while (fieldIndex.hasNext()) {
- // 获取需要分页的域
- String field = fieldIndex.next();
- // 过滤空白
- if (StringUtils.isBlank(field)) {
- continue;
- }
- // 获取值
- Object value = ((Map<String, Object>) object).get(field);
- // 写入doc
- document.add(new Field(field, value.toString(), Field.Store.YES, Field.Index.TOKENIZED));
- }
- // 写入索引文件
- indexWriter.addDocument(document);
- }//while
- }//for
- } catch (CorruptIndexException e) {
- logger.error("create index failed!", e);
- throw new RuntimeException("create index failed!",e);
- } catch (LockObtainFailedException e) {
- logger.error("create index failed!", e);
- throw new RuntimeException("create index failed!",e);
- } catch (IOException e) {
- logger.error("create index failed!check the authentation!", e);
- throw new RuntimeException("create index failed!",e);
- } catch (ClassNotFoundException e) {
- logger.error("class not found!", e);
- throw new RuntimeException("create index failed!",e);
- }
- }
- /**
- * 查询服务
- * @param keywords 查询字
- * @return
- */
- public List<Map<String,String>> search(String keywords){
- //~~~ return value
- List<Map<String,String>> result = new ArrayList<Map<String,String>>();
- try {
- // 搜索执行器
- IndexSearcher indexSearcher = new IndexSearcher(defaultIndexDir);
- // 分词器
- Analyzer analyzer = new CJKAnalyzer();
- // 关键字列表
- List<String> keyWordsList = getKeyWordsList();
- for(String indexName : keyWordsList){
- QueryParser queryParser = new QueryParser(indexName,analyzer);
- Query query = queryParser.parse(keywords);
- Hits hits = indexSearcher.search(query);
- if(logger.isDebugEnabled()){
- logger.debug("search result count:"+hits.length());
- }
- for(int i=0;i<hits.length();i++){
- Document document = hits.doc(i);
- Map<String,String> resultMap = new HashMap<String,String>();
- for(String field : keyWordsList){
- if(StringUtils.isBlank(field)){
- continue;
- }
- String value = document.get(field);
- resultMap.put(field, value);
- }
- result.add(resultMap);
- }
- }
- } catch (CorruptIndexException e) {
- logger.error("create index failed!", e);
- throw new RuntimeException("create index failed!",e);
- } catch (LockObtainFailedException e) {
- logger.error("create index failed!", e);
- throw new RuntimeException("create index failed!",e);
- } catch (IOException e) {
- logger.error("create index failed!check the authentation!", e);
- throw new RuntimeException("create index failed!",e);
- } catch (ClassNotFoundException e) {
- logger.error("class not found!", e);
- throw new RuntimeException("create index failed!",e);
- } catch (ParseException e) {
- logger.error("parse keyword exception!", e);
- throw new RuntimeException("parse keyword exception!",e);
- }
- return result;
- }
- /**
- * 获取配置的主键名称
- */
- @SuppressWarnings({ "rawtypes", "unchecked" })
- private String getKeyName() throws ClassNotFoundException{
- Class objectClass = Class.forName(className);
- Table table = (Table) objectClass.getAnnotation(Table.class);
- String keyName = table.keyField();
- return StringUtils.isBlank(keyName)? DEFAULT_ID : keyName.toLowerCase();
- }
- /**
- * 根据配置的关键字串获取关键字列表,自动补全主键
- */
- private List<String> getKeyWordsList() throws ClassNotFoundException{
- // 获取需要建立索引的域
- String[] fields = StringUtils.split(fieldsStr.toLowerCase(), DEFAULT_KEY_FIELD_SPLIT);
- // 转换成列表形式
- List<String> fieldList = Arrays.asList(fields);
- // 如果配置的索引字段串不包含主键,则手动添加主键,也就是主键必须创建索引用于标示doc
- String keyName = getKeyName();
- if(!fieldList.contains(keyName)){
- fieldList.add(keyName);
- }
- return fieldList;
- }
- // ~~~~~~~~~~~~~~~~~~~~~getter && setter ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~***//
- public void setClassName(String className) {
- this.className = className;
- }
- public void setFieldsStr(String fieldsStr) {
- this.fieldsStr = fieldsStr;
- }
- public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
- this.jdbcTemplate = jdbcTemplate;
- }
- public void setDefaultIndexDir(String defaultIndexDir) {
- this.defaultIndexDir = defaultIndexDir;
- }
- }
这个是我们的中间件,提供的是索引建立、索引更新和搜索服务,其中有几个变量如className指明了要针对哪个Do对象所对应的表来建立全文检索,fieldsStr指明了需要建立索引的字段,以逗号分开,其他的参数也都很简单。
我们需要根据配置的字段去查询数据库已完成索引的建立工作,那么如何获取呢?我们说过不希望通过显示的业务代码来完成,所以这里使用了前段时间开发的一个jdbcTemplate的封装抽象类,其中的查询字段列表的方法来得到特定字段值的列表,为了不显示的使用业务DAO,我们使用了一个匿名内部类来完成。具体的过程可以参见前面的文章。其中与之有所不同的是,在删除索引的时候,我们需要有一个类似于记录ID的标志位来标记唯一约束,以防止误删的情况,这里由于数据来自于数据库,所以很自然的我们选择使用数据库记录ID,如果用户配置的索引串中没有配置ID,那我们将通过注解的方式自动获取其配置在DO类中的ID名称,如果注解中也没配置,那么将使用默认的名称“id”。
看看我们的注解:
- package com.normandy.position.common;
- import java.lang.annotation.Documented;
- import java.lang.annotation.ElementType;
- import java.lang.annotation.Retention;
- import java.lang.annotation.RetentionPolicy;
- import java.lang.annotation.Target;
- /**
- * 针对于数据库表的配置的注解
- *
- * 工程名称:NormandyPosition
- * 类型名称:Table
- * 概要:
- * <p> 目前主要用于配置数据库表名,主键名 </p>
- * 创建时间:2010-7-28 上午10:40:42
- * 创建人:quzishen
- * 最后修改时间:2010-7-28 上午10:40:42
- * 最后修改内容:
- * @version 1.0
- */
- @Documented
- @Retention(RetentionPolicy.RUNTIME)
- @Target(ElementType.TYPE)
- public @interface Table {
- /**~~~~ 表名*/
- String tableName() default("table");
- /**~~~~ 主键名称*/
- String keyField() default("id");
- }
看看用法:
- package com.normandy.position.domain;
- import java.io.Serializable;
- import java.util.Date;
- import com.normandy.position.common.Table;
- @Table(tableName = "NOR_QUICK_NEWS",keyField="id")
- public class NorQuickNews implements Serializable {
- private static final long serialVersionUID = -4777096683339361256L;
- private long id;
- private String prop1;
- private String prop2;
- private String prop3;
- private String prop4;
- private String prop5;
- private String prop6;
- private String prop7;
- private String prop8;
- private String prop9;
- private String name;
- private Date gmt_Create;
- public long getId() {
- return id;
- }
- public void setId(long id) {
- this.id = id;
- }
- public String getProp1() {
- return prop1;
- }
- public void setProp1(String prop1) {
- this.prop1 = prop1;
- }
- public String getProp2() {
- return prop2;
- }
- public void setProp2(String prop2) {
- this.prop2 = prop2;
- }
- public String getProp3() {
- return prop3;
- }
- public void setProp3(String prop3) {
- this.prop3 = prop3;
- }
- public String getProp4() {
- return prop4;
- }
- public void setProp4(String prop4) {
- this.prop4 = prop4;
- }
- public String getProp5() {
- return prop5;
- }
- public void setProp5(String prop5) {
- this.prop5 = prop5;
- }
- public String getProp6() {
- return prop6;
- }
- public void setProp6(String prop6) {
- this.prop6 = prop6;
- }
- public String getProp7() {
- return prop7;
- }
- public void setProp7(String prop7) {
- this.prop7 = prop7;
- }
- public String getProp8() {
- return prop8;
- }
- public void setProp8(String prop8) {
- this.prop8 = prop8;
- }
- public String getProp9() {
- return prop9;
- }
- public void setProp9(String prop9) {
- this.prop9 = prop9;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public Date getGmt_Create() {
- return gmt_Create;
- }
- public void setGmt_Create(Date gmt_Create) {
- this.gmt_Create = gmt_Create;
- }
- }
配置文件中,我们添加相关的配置:
- <bean id="luceneSearcher" class="com.normandy.position.common.LuceneSearcher" depends-on="jdbcTemplate">
- <property name="defaultIndexDir">
- <value>${lucene.index.dir}</value>
- </property>
- <property name="className" value="com.normandy.position.domain.NorQuickNews" />
- <property name="fieldsStr" value="id,prop1,prop2" />
- </bean>
为了完成自动刷新,我们添加配置任务:
- <bean id="timetaskScheduler"
- class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
- <property name="triggers">
- <list>
- <ref local="luceneTrigger" />
- </list>
- </property>
- <property name="autoStartup">
- <value>true</value>
- </property>
- <property name="schedulerName">
- <value>timetaskScheduler</value>
- </property>
- </bean>
- <bean id="luceneTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
- <property name="jobDetail">
- <bean
- class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
- <property name="targetObject" ref="luceneSearcher" />
- <property name="targetMethod" value="refreshIndex" />
- </bean>
- </property>
- <property name="cronExpression" value="0 */15 * * * ?" />
- </bean>
这样就完成了开发工作。系统启动先建立索引,然后每隔15分钟将刷新一次索引。自动刷新我们的策略是删除所有的索引,重新建立索引,这个特别针对于数据量较小的操作,如果数据量稍微大一些,不推荐采用这种方式,我们需要进行一个自动识别的工作,只刷新变更过的记录,而不要过多的开销系统来重新全部重建。这里由于数据量较小,所以为了方便,我们直接删除所有索引重新建立。在后续的工作中,我们将重点研究如何精准刷新。
看看单元测试代码:
- package com.normandy.positiontest;
- import java.util.List;
- import java.util.Map;
- import junit.framework.TestCase;
- import org.springframework.context.ApplicationContext;
- import org.springframework.context.support.ClassPathXmlApplicationContext;
- import org.springframework.jdbc.core.JdbcTemplate;
- import com.normandy.position.common.LuceneSearcher;
- public class LuceneSearcherTest extends TestCase {
- private LuceneSearcher luceneSearcher;
- private JdbcTemplate jdbcTemplate;
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
- "com/normandy/positiontest/applicationContext.xml");
- luceneSearcher = (LuceneSearcher) applicationContext
- .getBean("luceneSearcher");
- jdbcTemplate = (JdbcTemplate)applicationContext.getBean("jdbcTemplate");
- luceneSearcher.setJdbcTemplate(jdbcTemplate);
- }
- public void testInit(){
- luceneSearcher.initIndex();
- }
- public void testRemoveAllIndex(){
- luceneSearcher.removeAllIndex();
- }
- public void testRefreshIndex(){
- luceneSearcher.refreshIndex();
- }
- public void testSearch(){
- List<Map<String,String>> list = luceneSearcher.search("prop1");
- System.out.println(list.size());
- }
- }
万里长征只走了第一步,剩下的事情还有很多,这个只是最简单的一个例子,万事开头难,在熟练应用的基础上进行二次开发或者源码分析将是接下来的主要工作思路。
相关推荐
《Lucene使用代码实例之搜索文档》 Lucene是一个高性能、全文检索库,它提供了强大的文本分析和索引功能,广泛应用于搜索引擎开发和其他需要高效文本处理的场景。本篇文章主要面向初学者,通过实例详细解释如何使用...
### Lucene对XML文档建立索引的技术解析与实践 #### 一、引言 随着互联网技术的迅猛发展,非结构化数据(如XML文档)在企业和组织中的应用日益广泛。如何高效地处理这些非结构化的数据,特别是进行快速检索成为了一...
Lucene 提供了索引和搜索文档的基本框架,包括分词、建立倒排索引、查询解析、结果排序等功能。 **安装与配置** 在使用 Lucene 之前,首先需要将其添加到项目依赖中。如果你使用的是 Maven,可以在 pom.xml 文件中...
**使用Lucene对数据库建立索引及搜索** Lucene是一个高性能、可伸缩的信息检索库,它是Apache软件基金会的顶级项目之一。它提供了一个简单但功能强大的API,用于在各种数据源上创建全文搜索引擎,包括数据库。在本...
《使用Lucene.NET对数据库建立索引及搜索》 在信息技术领域,搜索引擎是不可或缺的一部分,尤其是在处理大量数据时。Lucene.NET是一个强大的全文搜索引擎库,它允许开发人员在应用程序中集成高级搜索功能。本文将...
**Lucene简介** Lucene是Apache软件基金会的一个开放源代码项目,它是一个高性能、全文本检索库,...通过上述步骤,你可以在MyEclipse10环境下使用Lucene快速建立和搜索索引,为你的应用程序添加强大的全文检索功能。
2. **分词(Tokenization)**: Lucene使用Analyzer对输入文本进行分词,将长句子拆分成独立的单词或短语,这是建立索引的基础。 3. **文档(Document)**: 在Lucene中,每个要搜索的信息被视为一个Document,包含多...
Lucene的使用者不需要深入了解有关全文检索的知识,仅仅学会使用库中的一个类,你就为你的应用实现全文检索的功能. 不过千万别以为Lucene是一个象google那样的搜索引擎,Lucene甚至不是一个应用程序,它仅仅是一个工具,...
对于站内搜索,例如在BBS或BLOG中,你可以创建一个索引,将所有文章的标题和内容作为字段,利用Lucene的API建立索引。当用户输入搜索词时,通过查询API查找匹配的文章,并按照相关性返回结果。 ### 五、总结 ...
【Lucene简介】 Lucene是一个基于Java的全文信息检索工具包,它被广泛应用于构建搜索引擎和文本检索系统。...通过理解Lucene的基本原理和使用方法,我们可以构建出高效、灵活的全文搜索引擎,满足各种信息检索需求。
**Lucene.NET结合Sql建立全文检索** Lucene.NET是一个开源的全文搜索引擎库,它是Apache Lucene项目的一部分,专为.NET Framework设计。这个项目的目标是提供一个高性能、可扩展且易于使用的全文检索API。在.NET...
本文将深入探讨如何使用Lucene.NET进行全文搜索,特别是针对多关键字匹配的场景。 首先,我们需要了解Lucene.NET的基本概念。Lucene是一个开源的文本搜索库,它的核心功能包括文档索引、搜索和排序。Lucene.NET是这...
4. 使用Lucene建立索引系统。这部分工作包括为抓取的数据建立全文索引,并对索引进行优化,以支持快速有效的搜索。 5. 设计用户界面。一个友好的用户界面对于用户体验至关重要,需要设计直观、易用的搜索界面。 6. ...
通过以上分析,我们可以看出这个“Lucene 高级搜索项目”全面覆盖了Lucene的核心技术,从基础的索引创建到复杂的附件搜索和全文搜索,再到插件开发和Solr的使用,为学习和实践Lucene提供了丰富的素材。
总之,使用Lucene和MySQL联合开发的Java应用程序能够提供高效、灵活的搜索体验,这对于处理大量结构化文本数据的系统来说是极其重要的。通过掌握这种技术,开发者可以构建出满足用户需求的高性能搜索解决方案。
在这个场景中,我们将探讨如何利用Lucene来检索文本并建立索引,同时结合Struts框架构建一个Web程序。 首先,**Lucene** 是一个开源的Java库,它提供了完整的搜索功能,包括分词、索引创建、查询解析和结果排序。它...
- 使用Lucene的Analyzer对文本进行分词处理。 - 建立倒排索引并保存到硬盘或内存中。 - 实现搜索接口,用户输入关键词后,通过Lucene的QueryParser解析查询,并执行搜索。 - 返回搜索结果,通常会结合SpringMVC...
《Lucene全文检索:简单索引与搜索实例详解》 Lucene是Apache软件基金会的开源项目,是一款强大的全文检索库,被广泛应用于Java开发中,为开发者提供了构建高性能搜索引擎的能力。在本文中,我们将深入探讨如何基于...