http://moshalanye.iteye.com/blog/281379
这篇文章的由来是在寻求lucene的搜索的性能提高的过程中成形的,
感谢所有所以给于我帮助的朋友,在baseworld的提示下,我仔细翻阅了代码,
于是想把自己的一些收获和想法写出来,希望对在学习的人提供帮助,
更希望有人不吝啬手中的砖头,指正我的想法
FieldCache为FieldCacheImpl的接口,
其中有个名为default类型为FieldCacheImpl的静态对象,
FieldCacheImpl中包含一些重要的不同类型的Cache属性,
例如:bytesCache,stringsCache。。。
均继承于FieldCacheImpl.Cache抽象类,均实现抽象接口
protected abstract Object createValue(IndexReader reader, Object key)
throws IOException;
例如:
- Cache bytesCache = new Cache() {
- protected Object createValue(IndexReader reader, Object entryKey)
- throws IOException {
- /*获得需要处理的域的信息* /
- Entry entry = (Entry) entryKey;
- /*获得需要处理的域名*/
- String field = entry.field;
- /*获得类型转换器,将String转成byte*/
- ByteParser parser = (ByteParser) entry.custom;
- /*声明缓存的数据,注意该数据声明的大小为reader。maxDoc(),即最大Doc*/
- final byte[] retArray = new byte[reader.maxDoc()];
- /*获得通过term查找包含其的doc号*/
- TermDocs termDocs = reader.termDocs();
- /*term的循环器,参数为new Term (field, ""),即从该域开始循环*/
- TermEnum termEnum = reader.terms (new Term (field, ""));
- try {
- do {
- /*开始loop*/
- Term term = termEnum.term();
- if (term==null || term.field() != field) break;
- /*将term中的text转换成byte*/
- byte termval = parser.parseByte(term.text());
- /*寻找包含该term的doc*/
- termDocs.seek (termEnum);
- while (termDocs.next()) {
- /*
- 并把retArray数组该doc号index下的值赋值。
- 在此处注意!!如果一个doc包含多个term,
- 那么意味着该retArray[termDocs.doc()]会被覆盖,
- 值将是最后次赋的值,也就是说cache的数据是限制了doc包含的数据个数据的,
- doc包含1个以上,cache便起不到真正的效果了,所以使用该cache的场景有限,
- 我同样也考虑过改写cache,让其支持doc包含多个数据的情况,
- 但突然发现这个跟把Index的数据全缓存在内存中没有什么区别! 矛盾啊!
- 可能我的思路 还是受了限制阿!希望有人能给个思路 :D
- */
- retArray[termDocs.doc()] = termval;
- }
- } while (termEnum.next());
- } finally {
- termDocs.close();
- termEnum.close();
- }
- /*返回缓存的数据*/
- return retArray;
- }
- };
- static class Entry {
- final String field; /*which Fieldable 域名*/
- final int type; /*which SortField type 用于转换的的类型*/
- final Object custom; /* which custom comparator 转换器*/
- final Locale locale; /*
- the locale we're sorting (if string)
- 国际化的咚咚
- */
以下是Cache的get的方法
Cache的get方法
- public Object get(IndexReader reader, Object key) throws IOException {
- /*零时变量,存放该reader对应的缓存数据*/
- Map innerCache;
- Object value;
- /*readerCache缓存不通的reader读取的缓存数据*/
- synchronized (readerCache) {
- /*获取该reader对应的缓存数据*/
- innerCache = (Map) readerCache.get(reader);
- if (innerCache == null) {
- /*当没有该reader的缓存数据时,声明一个HashMap来存放该reader的缓存数据*/
- innerCache = new HashMap();
- /*放入到readerCache中,此时Map中没有数据*/
- readerCache.put(reader, innerCache);
- /*返回数据为null*/
- value = null;
- } else {
- value = innerCache.get(key);
- }
- if (value == null) {
- /*创建value的容器*/
- value = new CreationPlaceholder();
- /*并将容器放入对应的地方*/
- innerCache.put(key, value);
- }
- }
- if (value instanceof CreationPlaceholder) {
- /*
- 对容器进行synchronized!该处是一个小技巧,在同步缓存数据时,
- 有可能该值为null,对null不能同步,于是包了个马甲,马甲不为Null,
- 马甲中的值为null
- */
- synchronized (value) {
- CreationPlaceholder progress = (CreationPlaceholder) value;
- if (progress.value == null) {
- /*当容器中缓存数据不存在!开始缓存!*/
- progress.value = createValue(reader, key);
- /*
- 修改马甲,把马甲脱掉!由于修改了readerCache所以对它同步!
- 我也有个疑惑?是否可以只对innerCache就可以了
- */
- synchronized (readerCache) {
- innerCache.put(key, progress.value);
- }
- }
- return progress.value;
- }
- }
- return value;
- }
ExtendedFieldCacheImpl 继承也只是扩展了以下
class ExtendedFieldCacheImpl extends FieldCacheImpl implements ExtendedFieldCache
下面介绍下FieldCache的应用场景,首先撤下题外话引一下
HitQueue extends 优先权队列(优先队列就不介绍了,有时间可以看看代码)
- final class HitQueue extends PriorityQueue {
- /*初始化HitQueue时,初始化队列大小*/
- HitQueue(int size) {
- initialize(size);
- }
- /*重点方法,优先级别判断, ## doc的排序由它而来 ##*/
- protected final boolean lessThan(Object a, Object b) {
- ScoreDoc hitA = (ScoreDoc)a;
- ScoreDoc hitB = (ScoreDoc)b;
- /*通过得分来判断,得分高的排前*/
- if (hitA.score == hitB.score)
- return hitA.doc > hitB.doc;
- else
- return hitA.score < hitB.score;
- }
- }
- /*
- collector负责收集结果集,继承抽象类HitCollector,
- 包含重要内部对象PriorityQueue hq
- */
- public class TopDocCollector extends HitCollector {
- /*零时变量,用以申明零时ScoreDoc的引用*/
- private ScoreDoc reusableSD;
- int totalHits;
- PriorityQueue hq;
- /** Construct to collect a given number of hits.
- * @param numHits the maximum number of hits to collect
- */
- /*默认构造HitQueue,即靠doc得分进行排序的优先队列*/
- public TopDocCollector(int numHits) {
- this(numHits, new HitQueue(numHits));
- }
- /*重点方法,获取topDocs*/
- public TopDocs topDocs() {
- ScoreDoc[] scoreDocs = new ScoreDoc[hq.size()];
- /*loop-- 从优先队列中获取数据*/
- for (int i = hq.size()-1; i >= 0; i--) // put docs in array
- scoreDocs[i] = (ScoreDoc)hq.pop();
- float maxScore = (totalHits==0)
- ? Float.NEGATIVE_INFINITY
- : scoreDocs[0].score;
- /*返回TopDocs对象*/
- return new TopDocs(totalHits, scoreDocs, maxScore);
- }
开始进入正题吧!
FieldSortedHitQueue同样继承 优先队列
- public class FieldSortedHitQueue
- extends PriorityQueue {
- /*
- 参数reader作为该queue的参数,在这里面通过reader来获得比较器。
- 参数fields作为排序的参数,排序的域
- 参数size还是老规矩麻,来指定queue的大小
- */
- public FieldSortedHitQueue (
- IndexReader reader,
- SortField[] fields,
- int size
- )
- throws IOException {
- final int n = fields.length;
- /*有几个比较的域,则申明几个比较器*/
- comparators = new ScoreDocComparator[n];
- this.fields = new SortField[n];
- /*循环获得比较器,并重新初始化this.fields*/
- for (int i=0; i<n; ++i) {
- String fieldname = fields[i].getField();
- comparators[i] = getCachedComparator (
- reader,
- fieldname,
- fields[i].getType(),
- fields[i].getLocale(),
- fields[i].getFactory()
- );
- if (comparators[i].sortType() == SortField.STRING) {
- this.fields[i] = new SortField (
- fieldname, fields[i].getLocale(), fields[i].getReverse()
- );
- } else {
- this.fields[i] = new SortField (
- fieldname, comparators[i].sortType(), fields[i].getReverse()
- );
- }
- }
- /*初始化queue大小*/
- initialize (size);
- }
在此介绍比较重要的方法,也是为什么会用到FieldCache的方法该方法用以获得比较器。
还记得FieldCacheImpl的那个件马甲中的东西是什么吗?如果不记得,在这里先提醒下,
那个是一个maxDoc长度的数组,数组的类型看你要的东西是什么类型的
- static ScoreDocComparator getCachedComparator (
- IndexReader reader,
- String field,
- int type,
- Locale locale,
- SortComparatorSource factory
- )
- throws IOException {
- /*当需要的是doc的比较类型,则给的是默认的coreDocComparator.INDEXORDER*/
- if (type == SortField.DOC) return ScoreDocComparator.INDEXORDER;
- /*当需要的是score的比较类型,则给的是默认的coreDocComparator.RELEVANCE*/
- if (type == SortField.SCORE) return ScoreDocComparator.RELEVANCE;
- /*
- 初始化查缓存的参数,factory不为null,则用factory初始化,
- 否则用type和locale来构造
- */
- FieldCacheImpl.Entry entry = (factory != null)
- ? new FieldCacheImpl.Entry (field, factory)
- : new FieldCacheImpl.Entry (field, type, locale);
- /*
- 此处的comparators是个什么列??赫赫! 按名字看貌似是一个装比较器的cache,
- 进入看看他的做法,看它怎么cache比较器吧!
- */
- return (ScoreDocComparator)comparators.get(reader, entry);
- }
又一个cache!装比较器的cache,我们来看看其中的代码,
get方法都是cache抽象类中的,这里就不解释了
- static final FieldCacheImpl.Cache Comparators = new FieldCacheImpl.Cache() {
- protected Object createValue(IndexReader reader, Object entryKey)
- throws IOException {
- /* 转参数entryKey; */
- FieldCacheImpl.Entry entry = (FieldCacheImpl.Entry) entryKey;
- /* 获得排序的field */
- String fieldname = entry.field;
- /* 获得排序的值类型,也可以说是将field的值转换后的类型 */
- int type = entry.type;
- /* 获取locale */
- Locale locale = entry.locale;
- /* 获取制造比较器的工厂,一般用于自定义比较器的生产 */
- SortComparatorSource factory = (SortComparatorSource) entry.custom;
- /* 返回的比较器 */
- ScoreDocComparator comparator;
- /* 根据type来获得比较器 */
- switch (type) {
- case SortField.AUTO:
- comparator = comparatorAuto (reader, fieldname);
- break;
- case SortField.INT:
- comparator = comparatorInt (reader, fieldname);
- break;
- case SortField.FLOAT:
- comparator = comparatorFloat (reader, fieldname);
- break;
- case SortField.LONG:
- comparator = comparatorLong(reader, fieldname);
- break;
- case SortField.DOUBLE:
- comparator = comparatorDouble(reader, fieldname);
- break;
- case SortField.STRING:
- if (locale != null) comparator =
- comparatorStringLocale (reader, fieldname, locale);
- else comparator =
- comparatorString (reader, fieldname);
- break;
- case SortField.CUSTOM:
- comparator = factory.newComparator (reader, fieldname);
- break;
- default:
- throw new RuntimeException ("unknown field type: "+type);
- }
- return comparator;
- }
- };
我取其中的两个比较有代表性的解释
代表性一:
case SortField.INT:
comparator = comparatorInt (reader, fieldname);
- /* 该方法获得该field的int类型的比较器 */
- static ScoreDocComparator comparatorInt (
- final IndexReader reader,
- final String fieldname
- )
- throws IOException {
- /*
- 获取域fieldname.intern()使用默认string池中的对象,使对象开销变少,
- 鼓励这样的使用
- */
- final String field = fieldname.intern();
- /*
- 重点的重点!!!!千呼万唤始出来!在这里使用FieldCache.DEFAULT.getInts,
- 获取缓存中数据(缓存数据数组的长度为maxDoc,index为doc号,
- 值为该field的值转成了int)
- */
- final int[] fieldOrder = FieldCache.DEFAULT.getInts (reader, field);
- /* OK!开始以缓存数据为基础编制马甲ScoreDocComparator */
- return new ScoreDocComparator() {
- public final int compare (final ScoreDoc i, final ScoreDoc j) {
- final int fi = fieldOrder[i.doc];
- final int fj = fieldOrder[j.doc];
- if (fi < fj) return -1;
- if (fi > fj) return 1;
- return 0;
- }
- public Comparable sortValue (final ScoreDoc i) {
- return new Integer (fieldOrder[i.doc]);
- }
- public int sortType() {
- return SortField.INT;
- }
- };
- }
代表性二:
case SortField.CUSTOM:
comparator = factory.newComparator (reader, fieldname);
使用工厂来factory.newComparator 制造compatator
如果有人写过自定义排序,肯定会接触SortComparator,
而该类继承SortComparatorSource,即compatator得工厂,
让我们看看他的实现吧
- /*
- 该类是ScoreDocComparator的工厂类,然而又不是纯粹的工厂类,
- 加了一个转换数据的功能,所以名字有点四不像了
- */
- public abstract class SortComparator
- implements SortComparatorSource {
- // inherit javadocs
- public ScoreDocComparator newComparator (
- final IndexReader reader,
- final String fieldname
- )
- throws IOException {
- final String field = fieldname.intern();
- /*
- 获取可比较的类的对象数组,Integer Long 都是可比较的,
- 继承了comparable接口,这里的方法 FieldCache.DEFAULT.
- getCustom (reader, field, SortComparator.this)
- 参数三是将本类的调用对象传给了FieldCache.DEFAULT.getCustom()方法,
- 目的没别的!就是回调该 Comparable getComparable (String termtext)方法;
- */
- final Comparable[] cachedValues =
- FieldCache.DEFAULT.getCustom (reader, field, SortComparator.this);
- return new ScoreDocComparator() {
- public int compare (ScoreDoc i, ScoreDoc j) {
- return cachedValues[i.doc].compareTo (cachedValues[j.doc]);
- }
- public Comparable sortValue (ScoreDoc i) {
- return cachedValues[i.doc];
- }
- public int sortType(){
- return SortField.CUSTOM;
- }
- };
- }
- /**
- 抽象方法,用于把termtext转换成可比较的数据,即继承过Comparable的对象,
- 在factory中多了一个工具功能
- */
- protected abstract Comparable getComparable (String termtext);
- }
那么我们来顺藤摸瓜吧,找到了FieldCache.DEFAULT.getCustom (reader, field, SortComparator.this)方法调用了customCache,之前介绍过其他的cache(ByteCache),
但没有介绍customCache,其实他们的区别就是获取缓存的数据时,
将term的数据转换成custom想要的东西,即客户自定义!从而支持扩展
- Cache customCache = new Cache() {
- protected Object createValue(IndexReader reader, Object entryKey)
- throws IOException {
- Entry entry = (Entry) entryKey;
- String field = entry.field;
- /*
- 获取传过来的SortComparator对象
- (批评一下,名字取得够烂!纯粹迷幻名称! :shock:
- 又是工厂又是获取可比较值的工具转换类叫这个名字,把人晕的不轻,
- 害我多读好多遍代码)
- */
- SortComparator comparator = (SortComparator) entry.custom;
- final Comparable[] retArray = new Comparable[reader.maxDoc()];
- TermDocs termDocs = reader.termDocs();
- TermEnum termEnum = reader.terms (new Term (field, ""));
- try {
- do {
- Term term = termEnum.term();
- if (term==null || term.field() != field) break;
- /* 回调getComparable方法!获取可比较的值,其他的就不细说了! */
- Comparable termval = comparator.getComparable (term.text());
- termDocs.seek (termEnum);
- while (termDocs.next()) {
- retArray[termDocs.doc()] = termval;
- }
- } while (termEnum.next());
- } finally {
- termDocs.close();
- termEnum.close();
- }
- return retArray;
- }
- };
- sortField该构造函数支持自定义排序的,
- SortComparatorSource 即自定义排序的工厂类,
- 一般都是继承SortComparatorSource 的子类 SortComparator。
- public SortField (String field, SortComparatorSource comparator, boolean reverse) {
- this.field = (field != null) ? field.intern() : field;
- this.type = CUSTOM;
- this.reverse = reverse;
- this.factory = comparator;
- }
接着来介绍fieldcache的比较适用的场景吧!
第一个肯定是自定义排序了,但这个无需我们去使用,代码已经帮我们封装过了
第二个比较自由的就是filter了,面对doc的中存储某种type的域的情况,
通过type过滤时,这个时候就可以适当选择cache了
这个是我写的一个数字的filter,因为浮点数包括了整数,写了一个数字的filter
- public class DigitalFilter extends Filter {
- static final String digitalRegex = "^\\d*\\.?\\d*$";
- private Term t;
- public DigitalFilter(Term t) {
- this.t = t;
- }
- public BitSet bits(IndexReader reader) throws IOException {
- BitSet bitSet = new BitSet(reader.maxDoc());
- /* 在此处调用了cache的getFloats方法,ExtendedFieldCache只是扩展了FieldCache,增加了LongCache 和DoubleCache,其他一样 */
- float digital[] = ExtendedFieldCache.DEFAULT.getFloats(
- reader,
- t.field(),
- new FieldCache.FloatParser() {
- public float parseFloat(String string) {
- if (string.matches(digitalRegex))
- return Float.parseFloat(string);
- return 0;
- }
- });
- for (int i = 0; i < digital.length; i++) {
- if (digital[i] == Float.parseFloat(t.text()))
- bitSet.set(i);
- }
- return bitSet;
- }
- public static void main(String args[]) {
- }
- }
解析就到这里了!代码整理是由baseworld的提点,导致了这些的产生,
在此感谢所有给我建议的人,感谢你们的帮助,
如果有人在fieldcache有不错的用法,请不吝啬手上的砖头,给点启发!
谢谢!