一、概述
在查询的过程中,如果我们想然某些索引不被查询到,可以将相关索引删除,但是删除后如果在后面又需要让其被查询,这样需要重建索引,这样反反复复显然很麻烦。 于是我们可以自定义过滤器,控制部分查询权限。自定义过滤器原理其实很简单,就是为每个索引再创建一个标记,此标记只能是0或者1,当一个索引的标记为0时这个索引是不会被查询到的,反之则可以。所有的索引的标记就像一个bit序列一样。
二、入门(工程lucene_filter0
)
首先我们在工具类FileIndexUtil.java
中创建索引的方法中添加一个字段索引。
public static void index(boolean hasNew) {
IndexWriter writer = null;
try {
writer = new IndexWriter(directory, new IndexWriterConfig(
Version.LUCENE_35, new StandardAnalyzer(Version.LUCENE_35)));
if (hasNew) {
writer.deleteAll();//如果我们要新建索引,那么将之前创建的删除
}
File file = new File("E:/myeclipse/Lucene/somefile");
Document document = null;
Random random = new Random();
int index = 0;//这里为id创建一个索引
for (File f : file.listFiles()) {
//将每个索引的评分设置成一个随机数
int score = random.nextInt(600);
document = new Document();
document.add(new Field("id", String.valueOf(index++), Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS));
document.add(new Field("content", new FileReader(f)));
document.add(new Field("filename", f.getName(),
Field.Store.YES, Field.Index.NOT_ANALYZED));
document.add(new Field("path", f.getAbsolutePath(),
Field.Store.YES, Field.Index.NOT_ANALYZED));
document.add(new NumericField("date", Field.Store.YES, true)
.setLongValue(f.lastModified()));
// 最后我们将字节数转换成kb
document.add(new NumericField("size", Field.Store.YES, true)
.setIntValue((int) (f.length())));
//这里我们使用score评分来创建索引,没有存储,搜索出来的时候为null
//这里我自己随机设置的一个评分
document.add(new NumericField("score", Field.Store.NO, true).setIntValue(score));
writer.addDocument(document);
}
} catch (CorruptIndexException e) {
e.printStackTrace();
} catch (LockObtainFailedException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (writer != null) {
try {
writer.close();
} catch (CorruptIndexException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
说明:可以看到这里我们添加了id
的索引。然后重建索引。
定义一个查询方法类:
CustomFilter.java
package cn.itcast.util;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs;
public class CustomFilter {
public void searchByCustomFilter(){
try {
IndexSearcher searcher = new IndexSearcher(IndexReader.open(FileIndexUtil.getDirectory()));
Query q = new TermQuery(new Term("content", "java"));
TopDocs tds = null;
tds = searcher.search(q, new MyIdFilter(), 100);
for (ScoreDoc sd : tds.scoreDocs) {
Document doc = searcher.doc(sd.doc);
System.out.println("id:" + sd.doc + ",评分:" + sd.score
+ ",名称:" + doc.get("filename") + ",路径:" + doc.get("path")
+ ",文件大小:" + doc.get("size")
+ ", bit id: " + doc.get("id"));
}
searcher.close();
} catch (CorruptIndexException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
自定义过滤器MyIdFilter.java
package cn.itcast.util;
import java.io.IOException;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.TermDocs;
import org.apache.lucene.search.DocIdSet;
import org.apache.lucene.search.Filter;
import org.apache.lucene.util.OpenBitSet;
public class MyIdFilter extends Filter {
//存储要删除的id
private String[] delIds = {"10"};
@Override
public DocIdSet getDocIdSet(IndexReader reader) throws IOException {
//这就相当于为每个索引创建一个标记,比如设置标记为0不显示,设置为1显示,这样就达到了过滤的效果。
//此对象创建好之后所有对象都是0
OpenBitSet obs = new OpenBitSet(reader.maxDoc());
//obs.set(5);//将doc id设置为1
//先把元素填满,该文档的对应元素就会被设置为1
obs.set(0, reader.maxDoc() - 1);
int[] docs = new int[1];//存放位置
int[] freqs = new int[1];//次数
//获取id所在的位置并且将其设置为0
for(String delId : delIds){
//获取TermDocs
TermDocs tds = reader.termDocs(new Term("id", delId));
//会将查询出来的对象的位置存储到docs中,出现的频率存储在freqs,返回查询出来的条数
int count = tds.read(docs, freqs);
if(count == 1){//本来也只有一条数据
obs.clear(docs[0]);//将这个位置的元素删除
}
}
return obs;
}
}
测试TestCustomFilter.java
:
@Test
public void test01(){
CustomFilter filter = new CustomFilter();
filter.searchByCustomFilter();
}
说明:可以看到在CustomFiltet.java
中我们在创建搜索的时候将自定义过滤器传递进去了。这样就可以实现过滤查询。测试结果:
可以看到我们将
id
为10的索引给过滤掉了。但是这种写法显然不是很好,比如这里将要过滤的索引的id
都写死了。
三、改进(工程lucene_filter1
)
这里我们首先编写一个接口FilterAccessor.java
,此接口专门用来设置我们要处理的域、域相关的值、是否要处理。
package cn.itcast.util;
//这里存储过滤器要处理的内容,之前我们是写死的
public interface FilterAccessor {
public String[] values();//要处理的值,比如id值
public String getField();//表示要处理的域,比如id
public boolean set();//是否要进行处理(将值设置进去)
}
说明:这里的是否要处理的意思是,如果我们将一些id
值设置进去,就表示我们只想让这些id
索引被查询到。将其他的没有设置的索引过滤掉。
MyIdFilter.java
package cn.itcast.util;
import java.io.IOException;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.TermDocs;
import org.apache.lucene.search.DocIdSet;
import org.apache.lucene.search.Filter;
import org.apache.lucene.util.OpenBitSet;
public class MyIdFilter extends Filter {
// 存储要删除的id
private FilterAccessor accessor;
public MyIdFilter(FilterAccessor accessor) {
this.accessor = accessor;
}
@Override
public DocIdSet getDocIdSet(IndexReader reader) throws IOException {
OpenBitSet obs = new OpenBitSet(reader.maxDoc());
if(accessor.set()){
set(reader, obs);
}else {
clear(reader, obs);
}
return obs;
}
private void set(IndexReader reader, OpenBitSet obs) {
try {
int[] docs = new int[1];
int[] freqs = new int[1];
for (String delId : accessor.values()) {
TermDocs tds = reader.termDocs(new Term(accessor.getField(), delId));
int count = tds.read(docs, freqs);
if (count == 1) {
obs.set(docs[0]);//让标记为1
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void clear(IndexReader reader, OpenBitSet obs) {
try {
obs.set(0, reader.maxDoc() - 1);
int[] docs = new int[1];
int[] freqs = new int[1];
for (String delId : accessor.values()) {
TermDocs tds = reader.termDocs(new Term(accessor.getField(), delId));
int count = tds.read(docs, freqs);
if (count == 1) {
obs.clear(docs[0]);//让标记为0
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
说明:从这里可以看到,我们会将传递进来的值对应的索引的标记设置为1,让其被查询到。否则设置标记为0,这样便不能被查询到。
在类CustomFilter.java
中我们将相关的值传递进来:
public void searchByCustomFilter(){
try {
IndexSearcher searcher = new IndexSearcher(IndexReader.open(FileIndexUtil.getDirectory()));
Query q = new TermQuery(new Term("content", "java"));
TopDocs tds = null;
tds = searcher.search(q, new MyIdFilter(new FilterAccessor() {
//设置要处理的域的值,也就是我只想让下面的索引被查询到
@Override
public String[] values() {
//return new String[]{"1", "5", "10"};
return new String[]{"json.config", "json.ini", "json.ssh"};
}
//true表示要进行过滤处理
@Override
public boolean set() {
return true;
}
//设置要处理的域
@Override
public String getField() {
//return "id";
return "filename";
}
}), 100);
for (ScoreDoc sd : tds.scoreDocs) {
Document doc = searcher.doc(sd.doc);
System.out.println("id:" + sd.doc + ",评分:" + sd.score
+ ",名称:" + doc.get("filename") + ",路径:" + doc.get("path")
+ ",文件大小:" + doc.get("size")
+ ", bit id: " + doc.get("id"));
}
searcher.close();
} catch (CorruptIndexException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
说明:这里我们是使用了一个内部类,其实更好的做法是为每个域创建相关的实现类。然后传递进来。然后再次进行测试。