接上篇《3.lucene索引创建-域选项》 注:演示程序的lucene版本为8.0.0
上一篇我们进行了索引的域选项的讲解,以及对生成的索引文件的类型进行了解释,本篇继续来讲解对于索引的删除与更新操作。
一般系统存储的文件信息,有可能会被删除和更新,那么相关的索引文件也要进行删除和更新操作,来保证全文检索结果的正确性、实时性。
一、删除索引
下面我们来编写删除索引的代码:
public void delete(){
IndexWriter writer = null;
try {
writer = new IndexWriter(directory,new IndexWriterConfig(new StandardAnalyzer()));
//参数是一个选项,可以是一个Query,也可以是一个term,term是一个精确查找的值
//此时删除的文件并不会被完全删除,而是存储在一个回收站中的,可以被恢复
writer.deleteDocuments(new Term("id","1"));
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
if(writer!=null) writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
删除索引很简单,依然是使用IndexWriter对象来进行索引的操作,执行deleteDocuments方法,该方法可以传入一个Query,也可以是一个term,使用Query进行模糊和复合条件查找,使用Term进行精确查找。上面就使用Term查找索引的Filed域选项id为1的索引数据并删除。使用Query也类似,例如删除content内容包含junit的索引:
Analyzer analyzer = new StandardAnalyzer();//指定一个标准分词器,对文档内容进行分析
QueryBuilder queryBuilder = new QueryBuilder(analyzer);
//第一个参数是搜索的域,第二个参数是搜索域中要搜索的目标内容
Query query = queryBuilder.createPhraseQuery("content", "junit");
writer.deleteDocuments(query);
Query的拼装类似在第一节介绍的sreach方法中编写的一样。
编写执行删除方法的测试类:
@Test
public void testDelete(){
IndexUtil iu = new IndexUtil();
iu.delete();
}
执行该测试类,然后再执行之前的query方法,来查询现在索引个数和最大索引个数(这里多加了一个删除数的查询):
public void query(){
try {
IndexReader reader = DirectoryReader.open(directory);
System.out.println("numDocs:"+reader.numDocs());//文档数量
System.out.println("maxDocs:"+reader.maxDoc());//文档最大总数
System.out.println("deleteDocs:"+reader.numDeletedDocs());//被删除的文件总数
} catch (IOException e) {
e.printStackTrace();
}
}
查询结果:
发现当前索引个数为5,最大索引个数为6,被删除的索引个数为1。这里当前索引个数就是目前可使用的索引个数,而最大索引个数是包含了已删除的索引数,已删除的索引被存放在类似电脑回收站的地方,方便误删除之后的恢复工作。我们去索引库所在的硬盘中看目前的索引文件:
可以看到,多了一个文件“_0_1.liv”,该文件记录了当前段中被删除的文档号。所以被删除的所以信息都是在这个文件的(Lucene老版本是存在xxx.del文件中的);
注:删除所有的索引,可以使用IndexWriter的deleteAll方法,该方法会强制删除在索引库磁盘中的所有索引文件,此方法慎用。
二、回滚删除操作
那么既然被删除的文件是存储在“回收站”中的,那么肯定是可以恢复的,恢复起来也不难,使用indexWriter.rollback();进行恢复即可,但是要注意的是,恢复删除的操作必须和delete删除操作在一个事务中执行才有效果,单独执行是没有效果的。所以这里我们在delete之后进行commit提交删除动作的操作,然后在异常中进行操作的回滚:
public void delete(){
IndexWriter writer = null;
try {
writer = new IndexWriter(directory,new IndexWriterConfig(new StandardAnalyzer()));
//参数是一个选项,可以是一个Query,也可以是一个term,term是一个精确查找的值
//此时删除的文件并不会被完全删除,而是存储在一个回收站中的,可以被恢复
writer.deleteDocuments(new Term("id","1"));
writer.commit();//提交删除操作
} catch (IOException e) {
try {
writer.rollback();//出现异常回退删除操作
} catch (IOException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}finally{
try {
if(writer!=null) writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
老版本使用IndexReader的undeleteAll()方法恢复已删除的版本,在3.6之后的版本中已经没有这个方法了,我们目前使用的8.0版本同样也是没有的。想要恢复恢复只有通过indexWriter.rollback()方法,类似数据库的事务回滚。也即是说,目前新版本尚未提供直接回复删除索引的操作方法。
三、清空回收站
既然删除的索引被放置在“回收站”,那么肯定也可以将回收站中的垃圾清除,即直接彻底删除这个索引。而lucene对于这种彻底删除,执行的并不是直接的清除动作,而是一种“优化”操作,即对被删除的索引进行一个合并操作,在段文件中将这个删除后的索引进行一个空间上的优化,这里使用的就是IndexWriter的forceMergeDeletes方法:
//彻底清除已删除的索引
public void forceDelete(){
IndexWriter writer = null;
try {
writer = new IndexWriter(directory,new IndexWriterConfig(new StandardAnalyzer()));
writer.forceMergeDeletes();
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
if(writer!=null) writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
编写执行彻底删除方法的测试类:
@Test
public void testForceDelete(){
IndexUtil iu = new IndexUtil();
iu.forceDelete();
}
执行该测试类,然后再执行之前的query方法,查询文件个数:
可以看到,之前被删除的索引记录已经完全没有了。此时索引库liv文件也不见了,因为没有需要记录的被删除索引文件的文档ID了。
四、合并索引操作
当索引数目比较大的时候,lucene给我们提供的合并操作,用来将之前的多个子索引合并成一个大索引,实际上优化过程就是使用重新创建的索引来存放原来的多个子索引,索引中的文档也会重新编号。此时会大大节约磁盘的存储空间。
使用IndexWriter的forceMerge方法(参数为需要合并为几段索引)即可进行手动合并操作;
//优化合并索引
public void merge(){
IndexWriter writer = null;
try {
writer = new IndexWriter(directory,new IndexWriterConfig(new StandardAnalyzer()));
//会将索引合并为两段,这两段中被删除的数据会被清空
writer.forceMerge(2);
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
if(writer!=null) writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
实际上合并这个操作Lucene3.5之后的版本会在索引量达到一个等级的时候,自动帮我们优化,无需手动优化。而且在数据量很大的情况下,手动进行合并操作,会比较消耗性能,所以这里不建议频繁调用合并索引操作。
有关索引的更详细的底层实现,参见该文章:
javascript:void(0)
五、更新索引
当我们的目标检索内容发生改变,如文件内容。标题等被修改,相关的索引也要被更新。这里我们使用IndexWriter的updateDocument方法即可实现索引的更新:
//更新索引
public void update(){
IndexWriter writer = null;
try {
writer = new IndexWriter(directory,new IndexWriterConfig(new StandardAnalyzer()));
Document doc = new Document();
doc.add(new StringField("id","2",Store.YES));
doc.add(new StringField("email","bb@136.com",Store.YES));//原来是135,现在改为136
doc.add(new TextField("content","hello boy",Store.NO));//把"hello boy"改为"hello girl"
doc.add(new StoredField("name","tom2"));//把"tom"改为"tom2"
doc.add(new StoredField("attach",4));//把附件数量改为4
writer.updateDocument(new Term("id","2"), doc);//更新id为2的索引
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
if(writer!=null) writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
其实在lucene中并不存在直接的更新方法,其更新其实是做了“先删除、后添加”的操作,即是先将要更新的目标索引删除,然后再重新添加。上述方法就是先通过Term精确查找到相关索引内容,进行删除操作,然后再创建更新后的新的索引信息。
编写执行更新方法的测试类:
@Test
public void testUpdate(){
IndexUtil iu = new IndexUtil();
iu.update();
}
执行该测试类,然后再执行之前的query方法,查询文件个数:
可以看到,可用索引还是剩下的5个,而索引总数为6,且删除文件数为1,这就证实了Lucene的更新索引的操作就是做了“先删除、后添加”的操作。
然后我们编写searcher方法,查询id为2的记录是否被真正的修改:
/**
* 搜索
* */
public void searcher(){
IndexReader reader = null;
try {
reader = DirectoryReader.open(directory);
//根据IndexReader创建IndexSearcher
IndexSearcher indexSearcher = new IndexSearcher(reader);
//创建搜索的Query 类似SQL语句一样的查询串
Analyzer analyzer = new StandardAnalyzer();//指定一个标准分词器,对文档内容进行分析
QueryBuilder queryBuilder = new QueryBuilder(analyzer);
//第一个参数是搜索的域,第二个参数是搜索域中要搜索的目标内容
Query query = queryBuilder.createPhraseQuery("id", "2");
//根据searcher搜索并返回TopDocs
//第一个参数是查询对象,第二个参数是查询结果返回的最大值
TopDocs tds = indexSearcher.search(query, 10);
//根据TopDocs获取scoreDoc对象
ScoreDoc[] sds = tds.scoreDocs;//scoreDocs存储了搜索的结果集
for(ScoreDoc sd:sds){
//根据searcher和scoreDoc对象获取具体的Document对象
Document d = indexSearcher.doc(sd.doc);
//根据Document对象获取需要的值(输出邮箱、发件人名字、附件数量)
System.out.println(d.get("email")+"、"+d.get("name")+"、"+d.get("attach"));
}
} catch (IOException e) {
e.printStackTrace();
}finally{
//9、关闭reader
if(reader!=null){
try {
reader.close();//使用完IndexReader后要关闭
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
执行查询方法:
@Test
public void testSearch(){
IndexUtil iu = new IndexUtil();
iu.searcher();
}
执行结果:
说明我们的更新操作成功执行!
至此,索引的删除、彻底删除、合并以及更新操作介绍完毕。
参考:
《传智播客Lucene教学视频》