Bobo将field的数据缓存到内存中,与lucene中的FieldCache类的作用相似。
抽象类 FacetHandler<D> 是最基本的类,对于BoboIndexReader,它像一个工具,用于获取数据,然后存储到内存中。
以最简单的实现类SimpleFacetHandler做分析。SimpleFacetHandler只能做简单的归类,比如在电商应用中,一个Docment表示一个产品,其中一个field代表该商品的颜色,blue、red或者green,那么可以通过SimpleFacetHandler对搜索结果进行过滤,例如只要red颜色的商品~
class SimpleFacetHandler extends FacetHandler<FacetDataCache> implements FacetScoreable
其中load()函数用于IndexReader中获取Field数据,并将这些数据存储内存中,是Bobo初始化的主要工作。
public FacetDataCache load(BoboIndexReader reader) throws IOException {
FacetDataCache dataCache = new FacetDataCache();
dataCache.load(_indexFieldName, reader, _termListFactory);
return dataCache;
}
FacetDataCache、MultiValueFacetDataCache类是具体进行获取、存储数据的,load()是主要函数。
1、 其中FacetDataCache是指field中只能有一个term,也就是说该属性是唯一的,用上面电商商品的例子,就是一个
商品的颜色只能是blue或者red,而不能包括多个。
2、MultiValueFacetDataCache是field中可以有多个term,多个属性值的
FacetDataCache类的 load( )函数:
public void load(String fieldName,IndexReader reader,TermListFactory<T> listFactory) throws IOException
{
String field = fieldName.intern();
int maxDoc = reader.maxDoc();
//order实际上就是一个大数组,下标是docid,对应的值对应该文档的属性值(唯一的term)的index,通过index可以从freqlist、valArray等获取到该term的数据
BigSegmentedArray order = this.orderArray;
if (order == null) // we want to reuse the memory
{
order = newInstance(_termCountSize, maxDoc);
} else
{
order.ensureCapacity(maxDoc); // no need to fill to 0, we are reseting the
// data anyway
}
this.orderArray = order;
IntArrayList minIDList = new IntArrayList();
IntArrayList maxIDList = new IntArrayList();
IntArrayList freqList = new IntArrayList();
int length = maxDoc + 1;
TermValueList<T> list = listFactory == null ? (TermValueList<T>) new TermStringList()
: listFactory.createTermList();
TermDocs termDocs = reader.termDocs();
TermEnum termEnum = reader.terms(new Term(field, ""));
int t = 0; // current term number 就是上面order中对应的index,每个term对应自己的index
list.add(null);
minIDList.add(-1);
maxIDList.add(-1);
freqList.add(0);
// int df = 0;
t++;
try
{
do
{
Term term = termEnum.term();
if (term == null || term.field() != field)
break;
if (t > order.maxValue())
{
throw new IOException("maximum number of value cannot exceed: "
+ order.maxValue());
}
// store term text
// we expect that there is at most one term per document
if (t >= length)
throw new RuntimeException("there are more terms than "
+ "documents in field \"" + field
+ "\", but it's impossible to sort on " + "tokenized fields");
list.add(term.text());//将该term的text存到valArray 中
termDocs.seek(termEnum);
// freqList.add(termEnum.docFreq()); // doesn't take into account
// deldocs
int minID = -1;
int maxID = -1;
int df = 0;
if (termDocs.next())
{
df++;
int docid = termDocs.doc();
order.add(docid, t);
minID = docid;
while (termDocs.next())
{
df++;
docid = termDocs.doc();
order.add(docid, t);//记录下该doc对应的term
}
maxID = docid;
}
freqList.add(df);//添加term的df
minIDList.add(minID);//该term的倒排表中最小的docid
maxIDList.add(maxID);//该term的倒排表中最大的docid
t++;
} while (termEnum.next());
} finally
{
termDocs.close();
termEnum.close();
}
list.seal();
this.valArray = list;
this.freqs = freqList.toIntArray();
this.minIDs = minIDList.toIntArray();
this.maxIDs = maxIDList.toIntArray();
}
MultiValueFacetDataCache 的load()函数与 FacetDataCache基本一样,只是存储docid和index对应关系的地方不同,不是用大数组存储而是用 BufferedLoader 来存储。
- 下边分析一下BufferedLoader ,其成员 _info 是BigIntArray类型,就是个大数组
_buffer是BigIntBuffer类型,是一个动态分配的大数组
BigIntBuffer其实也相当于一个大数组,只不过是动态分配的,也算做了个hash,对数组分页,1024一个page
public class BigIntBuffer
{
private static final int PAGESIZE = 1024;
private static final int MASK = 0x3FF;
private static final int SHIFT = 10;
private ArrayList<int[]> _buffer;
private int _allocSize;
private int _mark;
public BigIntBuffer()
{
_buffer = new ArrayList<int[]>();
_allocSize = 0;
_mark = 0;//表示当前数组中当前可以存储数据的指针
}
public int alloc(int size)
{
if(size > PAGESIZE) throw new IllegalArgumentException("size too big");
if((_mark + size) > _allocSize)
{
int[] page = new int[PAGESIZE];//每次申请1024个int大小的内存,一页
_buffer.add(page);
_allocSize += PAGESIZE;
}
int ptr = _mark;
_mark += size;//将_mark指针向后移size个位置
return ptr;
}
public void reset()
{
_mark = 0;
}
public void set(int ptr, int val)
{
int[] page = _buffer.get(ptr >> SHIFT);
page[ptr & MASK] = val;
}
public int get(int ptr)
{
int[] page = _buffer.get(ptr >> SHIFT);
return page[ptr & MASK];
}
}
BufferedLoader类的add(int docid,int val)函数:
_info 是BigIntArray类型,就是个大数组,他申请的size是maxdoc的2倍,当field中的属性值(即term个数)小于等于2时,不需要_buffer存储,使用_info存储就可以了。大于2时就要使用_buffer了
//_info 是BigIntArray类型,就是个大数组
//_buffer是BigIntBuffer类型,是一个动态分配的大数组
public final boolean add(int id, int val)
{
int ptr = _info.get(id << 1);
if(ptr == EOD)
{
// 第一次插入,即插入id文档的第一个term
_info.add(id << 1, val);
return true;
}
int cnt = _info.get((id << 1) + 1);
if(cnt == EOD)
{
// 第二次插入,即插入id文档的第二个term
_info.add((id << 1) + 1, val);
return true;
}
if(ptr >= 0)
{
//此id的文档已经有2个term插入过了,那么要使用_buffer了
int firstVal = ptr;
int secondVal = cnt;
ptr = _buffer.alloc(SEGSIZE);//这里SEGSIZE等于8
_buffer.set(ptr++, EOD);//申请的内存的第一个位置填EOD,非EOD表示该id下term大于8,前面还有该id的term
_buffer.set(ptr++, firstVal);
_buffer.set(ptr++, secondVal);
_buffer.set(ptr++, val);
cnt = 3;
}
else
{
ptr = (- ptr);
if (cnt >= _maxItems) return false; // exceeded the limit
if((ptr % SEGSIZE) == 0)//意味着上一次申请的SEGSIZE个位置已经填满了
{
int oldPtr = ptr;//保存上一次申请的内存的指针
ptr = _buffer.alloc(SEGSIZE);//再申请一块SEGSIZE大小的内存
_buffer.set(ptr++, (- oldPtr));//将上一次啊申请内存指针写入新内存的第一个位置
}
_buffer.set(ptr++, val);//储存新加入的元素
cnt++;
}
_info.add(id << 1, (- ptr));
_info.add((id << 1) + 1, cnt);
return true;
}
//从id的文档中,读取对应的term(属性)
private final int readToBuf(int id, int[] buf)
{
int ptr = _info.get(id << 1);
int cnt = _info.get((id << 1) + 1);
int i;
if(ptr >=0)
{//ptr>0说明该文档的属性小于等于2(<2)
// read in-line data
i = 0;
buf[i++] = ptr;
if(cnt >= 0) buf[i++] = cnt;
return i;
}
// read from segments
//ptr<0 要从内存中读取term了
i = cnt;
while(ptr != EOD)
{
ptr = (- ptr) - 1;//得到最后的那个term的位置
int val;
while((val = _buffer.get(ptr--)) >= 0)//不是某次申请的SEGSIZE个位置的第一个位置
{
buf[--i] = val;
}
ptr = val;//如果这里的ptr不是EOD,那么指向上一个申请的内存块的最后一个位置
}
if(i > 0)
{
throw new RuntimeException("error reading buffered data back");
}
return cnt;
}