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 来存储。
_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; }