全文检索之lucene的优化篇--增删改查

主要介绍增删改查索引的功能,并且对于查询到的关键字,返回高亮的结果。高亮的效果,就是将查询出来的结果,在前后加上标签,<font
color="red">和</font>这样在浏览器显示的就是红色的字体.

目录效果如上,建立一个com.lucene的包,建立一个IndexDao的类,里面写入索引的增删改查方法;而建立的IndexDaoText类则是对这增删改查的测试;QueryResult则是一个查询结果的类,里面只有2个字段,总记录数和记录集合.

其中IndexDao类中的代码如下,比较长,其实也只是Search长,search长也只是因为查询前要做一些设置,排序,过滤器;查询后取出数据还要做一些高亮和摘要的设置.

package com.lucene;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import jeasy.analysis.MMAnalyzer;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.NumberTools;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriter.MaxFieldLength;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryParser.MultiFieldQueryParser;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.Filter;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.RangeFilter;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.highlight.Formatter;
import org.apache.lucene.search.highlight.Fragmenter;
import org.apache.lucene.search.highlight.Highlighter;
import org.apache.lucene.search.highlight.QueryScorer;
import org.apache.lucene.search.highlight.Scorer;
import org.apache.lucene.search.highlight.SimpleFragmenter;
import org.apache.lucene.search.highlight.SimpleHTMLFormatter;

public class IndexDao {

	//指定索引路径
	String indexPath = "F:\\Users\\liuyanling\\workspace\\LuceneDemo\\luceneIndex";

	//指定分词器
	//Analyzer analyzer = new StandardAnalyzer();
	 Analyzer analyzer = new MMAnalyzer();// 词库分词

	/**
	 * 添加/创建索引
	 *
	 * @param doc 需要创建索引的Document文件
	 */
	public void save(Document doc) {
		IndexWriter indexWriter = null;
		try {
			indexWriter = new IndexWriter(indexPath, analyzer, MaxFieldLength.LIMITED);
			indexWriter.addDocument(doc);
		} catch (Exception e) {
			throw new RuntimeException(e);
		} finally {
			try {
				indexWriter.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * 删除索引
	 *
	 * Term是搜索的最小单位,代表某个 Field 中的一个关键词,如:<title, lucene>
	 * new Term( "title", "lucene" ); //删除标题中带有lucene的索引
	 * new Term( "id", "5" );
	 * new Term( "id", UUID );
	 *
	 * @param term
	 */
	public void delete(Term term) {
		IndexWriter indexWriter = null;
		try {
			indexWriter = new IndexWriter(indexPath, analyzer, MaxFieldLength.LIMITED);
			indexWriter.deleteDocuments(term);
		} catch (Exception e) {
			throw new RuntimeException(e);
		} finally {
			try {
				indexWriter.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * 更新索引,也就是先删除后添加
	 *
	 * <pre>
	 * indexWriter.deleteDocuments(term);
	 * indexWriter.addDocument(doc);
	 * </pre>
	 *
	 * @param term
	 * @param doc
	 */
	public void update(Term term, Document doc) {
		IndexWriter indexWriter = null;
		try {
			indexWriter = new IndexWriter(indexPath, analyzer, MaxFieldLength.LIMITED);
			indexWriter.updateDocument(term, doc);
		} catch (Exception e) {
			throw new RuntimeException(e);
		} finally {
			try {
				indexWriter.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * <pre>
	 * totalPage = recordCount / pageSize;
	 * if (recordCount % pageSize > 0)
	 * 	totalPage++;
	 * </pre>
	 *
	 * @param queryString  关键字
	 * @param firstResult  从第几条索引开始查
	 * @param maxResults   最多查几条
	 * @return QueryResult 返回查询结果,包括总记录数和所有结果
	 */
	public QueryResult search(String queryString, int firstResult, int maxResults) {
		try {
			// 1,把要搜索的文本解析为 Query
			String[] fields = { "name", "content" };
			Map<String, Float> boosts = new HashMap<String, Float>();
			//创建索引时设置相关度,值越大,相关度越高,越容易查出来.name的优先级高于内容
			boosts.put("name", 3f);
		    boosts.put("content", 1.0f); //默认为1.0f

		    //构造QueryParser,设置查询的方式,以及查询的字段,分词器和相关度的设置
			QueryParser queryParser = new MultiFieldQueryParser(fields, analyzer, boosts);
			//查询关键字queryString的结果
			Query query = queryParser.parse(queryString);

			//返回查询的结果
			return search(query, firstResult, maxResults);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * 查询索引
	 * @param query         Query对象
	 * @param firstResult   从第几条索引开始查
	 * @param maxResults    最多查几条
	 * @return QueryResult  返回查询结果,包括总记录数和所有结果
	 */
	public QueryResult search(Query query, int firstResult, int maxResults) {
		IndexSearcher indexSearcher = null;

		try {
			// 2,进行查询
			indexSearcher = new IndexSearcher(indexPath);

			//查询的设置1:过滤器,只查询出size在200~1000的文件,这个影响效率,不建议使用
			//由于写成这样new RangeFilter("size","50", "200", true, true);,按理会查出50~200的文件,但是由于字符串的50是>200的,所以                                     //什么也不会查出来,所以要用NumberTools.longToString(),转换一下.
			Filter filter = new RangeFilter("size", NumberTools.longToString(200),NumberTools.longToString(1000),true,true);

			//查询的设置2:排序,设置根据size从小到大升序排序(不设置也可以的)
			Sort sort = new Sort();
			sort.setSort(new SortField("size")); // 默认为升序
			//sort.setSort(new SortField("size", true));

			//查询出结果
			TopDocs topDocs = indexSearcher.search(query, filter, 10000, sort);

			//将结果显示出来,收集到总记录数,实例化recordList,收集索引记录
			int recordCount = topDocs.totalHits;
			List<Document> recordList = new ArrayList<Document>();

			//准备高亮器,字体颜色设置为red
			Formatter formatter = new SimpleHTMLFormatter("<font color='red'>", "</font>");
			Scorer scorer = new QueryScorer(query);
			Highlighter highlighter = new Highlighter(formatter, scorer);

			//设置段划分器,指定关键字所在的内容片段的长度
			Fragmenter fragmenter = new SimpleFragmenter(50);
			highlighter.setTextFragmenter(fragmenter);

			// 3,取出当前页的数据
			//获取需要查询的最后数据的索引号
			int endResult = Math.min(firstResult + maxResults, topDocs.totalHits);
			for (int i = firstResult; i < endResult; i++) {
				ScoreDoc scoreDoc = topDocs.scoreDocs[i];
				int docSn = scoreDoc.doc; // 文档内部编号
				Document doc = indexSearcher.doc(docSn); // 根据编号取出相应的文档

				// 高亮处理,返回高亮后的结果,如果当前属性值中没有出现关键字,会返回 null
				// 查询“内容"是否包含关键字,没有则为null,有则加上高亮效果。
				String highContent = highlighter.getBestFragment(analyzer, "content", doc.get("content"));
				if (highContent == null) {
					//如果没有关键字,则设置不能超过50个字符
					String content = doc.get("content");
					int endIndex = Math.min(50, content.length());
					highContent = content.substring(0, endIndex);// 最多前50个字符
				}
				//返回高亮后的结果或者没有高亮但是不超过50个字符的结果
				doc.getField("content").setValue(highContent);

				//recordList收集索引记录
				recordList.add(doc);
			}

			// 返回结果QueryResult
			return new QueryResult(recordCount, recordList);
		} catch (Exception e) {
			throw new RuntimeException(e);
		} finally {
			try {
				indexSearcher.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

看完IndexDao的类的增删改查,还有测试这些增删改查的方法.按照之前的做法,在IndexDao一个类,就能写完测试方法,但是这里分开了.分开了的话,就算了解耦了,灵活性会好很多.

下面是IndexDaoTest的测试代码,

package com.lucene;

import org.apache.lucene.document.Document;
import org.apache.lucene.index.Term;
import org.junit.Test;

import com.lucene.units.File2DocumentUtils;

public class IndexDaoTest {
	//设置两个需要创建索引的文件
	String filePath = "F:\\Users\\liuyanling\\workspace\\LuceneDemo\\datasource\\IndexWriter addDocument's a javadoc .txt";
	String filePath2 = "F:\\Users\\liuyanling\\workspace\\LuceneDemo\\datasource\\小笑话_总统的房间 Room .txt";

	//indexDao,实现具体的增删改查方法
	IndexDao indexDao = new IndexDao();

	/**
	 * 测试保存方法
	 */
	@Test
	public void testSave() {
		Document doc = File2DocumentUtils.file2Document(filePath);
		//保存索引的时候设置相关度,相对于两个文件中都有的关键字,doc的相关度会高于doc2,也就是会优先查出来.
		doc.setBoost(3f);
		indexDao.save(doc);

		Document doc2 = File2DocumentUtils.file2Document(filePath2);
		//doc2.setBoost(1.0f);
		indexDao.save(doc2);
	}

	/**
	 * 测试删除索引
	 */
	@Test
	public void testDelete() {
		//根据路径,删除所有关于该路径的索引文件
		Term term = new Term("path", filePath);
		indexDao.delete(term);
	}

	/**
	 * 测试更新索引
	 */
	@Test
	public void testUpdate() {
		//将filePath路径下的索引更新,内容都改为"这是更新后的文件内容"
		Term term = new Term("path", filePath);

		Document doc = File2DocumentUtils.file2Document(filePath);
		doc.getField("content").setValue("这是更新后的文件内容");

		indexDao.update(term, doc);
	}

	/**
	 * 测试查询
	 */
	@Test
	public void testSearch() {
		//关键字为IndexWriter,房间和content:绅士(表示只在内容中查询绅士),这里
		String queryString1 = "IndexWriter";
		String queryString2 = "房间";
		String queryString3 = "content:绅士";

		printSearchResult(queryString1, 0, 10);
		printSearchResult(queryString2, 0, 10);
		printSearchResult(queryString3, 0, 10);
	}

	/**
	 * 由于想一次性测试上面的3个查询效果,所以提取了一个打印结果的方法
	 * @param queryString
	 * @param firstResult
	 * @param maxResults
	 */
	private void printSearchResult(String queryString,int firstResult, int maxResults) {
		QueryResult qr = indexDao.search(queryString, firstResult, maxResults);

		System.out.println("总共有【" + qr.getRecordCount() + "】条匹配结果");
		for (Document doc : qr.getRecordList()) {
			//对于size要改为System.out.println("size =" + NumberTools.stringToLong(doc.get("size")));将大小从字符串转为long
			File2DocumentUtils.printDocumentInfo(doc);
		}
	}

}

还有最后一个查询结果类,代码如下.

package com.lucene;

import java.util.List;

import org.apache.lucene.document.Document;

/**
 * 查询结果类,就像实体
 * @author liu
 *
 */
public class QueryResult {
	//总记录数
	private int recordCount;
	//索引记录
	private List<Document> recordList;

	//有参构造方法
	public QueryResult(int recordCount, List<Document> recordList) {
		super();
		this.recordCount = recordCount;
		this.recordList = recordList;
	}

	//get,set方法
	public int getRecordCount() {
		return recordCount;
	}

	public void setRecordCount(int recordCount) {
		this.recordCount = recordCount;
	}

	public List<Document> getRecordList() {
		return recordList;
	}

	public void setRecordList(List<Document> recordList) {
		this.recordList = recordList;
	}
}

代码看完了,现在看下运行效果.从增查改删依次测起.首先删了现有的索引文件夹.

1.执行添加,效果就是索引文件建立出来了,并且是两个文件都建立好了索引.

2.查看下结果.照理应该是三条都是有匹配结果的,但是第一条没有,是因为用了过滤器

把IndexDao中的search中配置的filter设置为null,就可以查出结果了.IndexWriter的size只有169,正好被过滤了.而且由于content中没有IndexWriter关键字,所以没有高亮,并且被摘要只有50个字符

3.然后执行以下改的方法,会将IndexWriter中内容修改.修改之后,照理只有一个结果,但是实际上查询结果是有两个记录,之前那条没有删除,然后新建了一条修改了.

4.删除,会删除indexWriter中的所有索引,还是2条记录,结果不对.

后来,发现原来

    Lucene在删除索引时,经常会出现代码成功执行,但索引并未正直删除的现象,总结一下,要注意以下因素:

1.在创建Term时,注意Term的key一定要是以"词"为单位,否则删除不成功,例如:添加索引时,如果把"d:\doc\id.txt"当作要索引的字符串索引过了,那么在删除时,如果直接把"d:\doc\id.txt"作为查询的key来创建Term是无效的,应该用Id.txt(但这样会把所有文件名为Id.txt的都删除,所以官方建议最好用一个能唯一标识的关键字来删除,比如产品编号,新闻编号等(我的猜想:每个document都加入一个id字段作为唯一标识(可用系统当前时间值作为id的值),每当要删除包含某关键字的文档索引时,先将这些文档搜索出来,然后获取它们的id值,传给一个List,然后再用List结合id字段来删除那些文档的索引......不过这个方法的效率可能会低了一点,因为每个文档都要搜两遍);

2.要删除的“词”,在创建索引时,一定要是Tokened过的,否则也不成功;

3.IndexReader,IndexModifer,IndexWriter都提供了DeleteDocuements方法,但建议用IndexModifer来操作,原因是IndexModifer内部做了很多线程安全处理;(PS:IndexModifer已经过期了)

4.删除完成后,一定要调用相应的Close方法,否则并未真正从索引中删除。

以上是网上查找的原因,最后实验出来为什么lucene的索引删不掉还是花了一点时间。首先是看下File2DocumentUtils中的索引字段的设置。

Document doc = new Document();
//将文件名和内容分词,建立索引,存储;而内容通过readFileContent方法读取出来。
doc.add(new Field("name",file.getName(),Store.YES,Index.ANALYZED));
doc.add(new Field("content",readFileContent(file),Store.YES,Index.ANALYZED));
//将文件大小存储,但建立索引,但不分词;路径则不需要建立索引
//doc.add(new Field("size",String.valueOf(file.length()),Store.YES,Index.NOT_ANALYZED));
doc.add(new Field("size",NumberTools.longToString(file.length()),Store.YES,Index.NOT_ANALYZED));
doc.add(new Field("path", file.getAbsolutePath(), Store.YES, Index.NO));

其中,name和content是ANALYZED的,而size和path分别是NOT_ANALYZED和NO.上面说要可以要Tokened的字段才能用,但是Tokened过期了,用ANALYZED替换了.而对于"d:\doc\id.txt",我开始搞不清楚是什么意思.后来发现是指如我的路径:F:\datasource\IndexWriter
addDocument‘sa javadoc .txt,按照正常思路,写成这样Term
term = new Term("path","F:\\datasource\\IndexWriteraddDocument‘s
a javadoc .txt");,但是term不认,写成这样反而认Term
term = new Term("path","IndexWriter addDocument‘s a javadoc .txt");我开始以为是这样,后来发现也不认。写成这样它才认Term
term = new Term("path","indexwriter");可以看出IndexWriter变成小写了,因为大写也不认。这就是上面说的key一定要是以"词"为单位.

对于Term到底是认哪种的写法,感到很奇怪.设想可能跟分词器有关,正好我之前我有用名字文本测试分词器,效果如下,但是我用的语句是这样的"IndexWriter
addDocument‘s a javadoc.txt",所以,我把文件名由"IndexWriter
addDocument‘s a javadoc
.txt"改成了"IndexWriter addDocument‘s ajavadoc.txt".

Term term = new Term("name","s");写成这样,测试发现MMAnalyzer分词器认这种写法,可以查出结果,但是StandardAnalyzer不认.所以对于极易分词器,写上indexwriter,adddocument,s,javadoc.txt都是可以的.

将删除和更新的方法重新写一下,term写成这样

Term term = new Term("name", "indexwriter");

进行测试,删除索引,重新创建索引,更新效果如下,只有一个语句了.

而删除效果,则全部删除了,一条不留.

最后,还没完,还有下篇《全文检索之lucene的优化篇--查询篇》,介绍lucene中的各种查询方法。

时间: 2024-09-30 09:23:04

全文检索之lucene的优化篇--增删改查的相关文章

全文检索之lucene的优化篇--分词器

在创建索引库的基础上,加上中文分词器的,更好的支持中文的查询.引入jar包je-analysis-1.5.3.jar,极易分词.还是先看目录. 建立一个分词器的包,analyzer,准备一个AnalyzerTest的类.里面的代码如下,主要写了一个testAnalyzer的方法,测试多种分词器对于中文和英文的分词;为了可以看到效果,所以写了个analyze()的方法,将分词器和text文本内容传入,并将分词的效果显示出来. package com.lucene.analyzer; import

全文检索之lucene的优化篇--创建索引库

在上一篇HelloWorld的基础上,建立一个directory的包,添加一个DirectoryTest的测试类,用来根据指定的索引目录创建目录存放指引. DirectoryTest类中的代码如下,基本上就是在HelloWorld的基础上改改就可以了. 里面一共三个方法,testDirectory(),测试创建索引库;testDirectoryFSAndRAM(),结合方法1的两种创建方式,优化;testDirectoryOptimize(),在方法2个基础上,研究索引的优化创建,减少创建的索引

lucene索引库的增删改查操作

1. 索引库的操作 保持数据库与索引库的同步 说明:在一个系统中,如果索引功能存在,那么数据库和索引库应该是同时存在的.这个时候需要保证索引库的数据和数据库中的数据保持一致性.可以在对数据库进行增.删.改操作的同时对索引库也进行相应的操作.这样就可以保证数据库与索引库的一致性. 工具类DocumentUtils 在对索引库进行操作时,增.删.改过程要把一个JavaBean封装成Document,而查询的过程是要把一个Document转化成JavaBean.在进行维护的工作中,要反复进行这样的操作

全文检索(二)-基于lucene4.10的增删改查

今天 用lucene完毕了 一个简单的web应用.提取了早期编写的一个測试类. 首先简单介绍下lucene几个经常使用包; lucene 包的组成结构:对于外部应用来说索引模块(index)和检索模块(search)是基本的外部应用入口 org.apache.Lucene.search/ 搜索入口 org.apache.Lucene.index/ 索引入口 org.apache.Lucene.analysis/ 语言分析器 org.apache.Lucene.queryParser/ 查询分析器

MongoDB入门篇--增删改查

在上篇博文mongodb已经成功启动:http://blog.csdn.net/u010773667/article/details/41847487,接下来就该进行一系列操作了.我们再开一个cmd,输入[mongo]命令打开shell即mongodb的客户端,默认连接的是"test"数据库,我这里设置集合(表)为student.图一: 1. 添加insert 语法:db.集合.insert({"Col1":"列值1","Col2&qu

pymongo学习第1篇——增删改查

参考文档: 1.https://docs.mongodb.org/getting-started/python/ 2.http://api.mongodb.org/python/current/api/pymongo/index.html # -*- coding: utf-8 -*- import sys from datetime import datetime from pymongo import MongoClient import pymongo import re def main

基础的增删改查,数据库优化,索引

mysql的特点 关系型数据库,免费使用, 插入式存储引擎, 性能高, 基础的增删改查 ddl语句,数据定义语句 123456789101112 create database test1;drop database test1;use test1;create table emp(ename varchar(10),hiredate date,sal decimal(10,2),deptno int(2));drop table emp;alter table emp modify ename

Lucene和Solr学习总结(3)增删改查

使用IndexSearcher,IndexWriter对象对索引进行增删改查 直接贴代码了,方便日后查看,回顾 public class LuceneManager {   private IndexWriter getIndexWriter() throws IOException { Directory directory = FSDirectory.open(new File("D://lucene//index"));//指定索引库存放位置Directory对象 Analyze

Vue电商后台管理系统项目第5篇-角色列表的增删改查&amp;&amp;角色授权

角色列表的增删改查 1.添加角色 先根据API文档编写接口: // 添加角色 export const addRolesApi = (data) => { return axios({ method: 'post', url: 'roles', data }) } 在角色组件内引用,然后给 添加角色 按钮绑定一个点击事件addRolesClick: <!-- 添加角色 --> <el-button type="success" plain @click=&quo