论坛首页 Java企业应用论坛

FieldCache在lucene中使用的代码解析,使用场景个人分析

浏览 4634 次
精华帖 (0) :: 良好帖 (1) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2008-11-28   最后修改:2008-11-28
这篇文章的由来是在寻求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有不错的用法,请不吝啬手上的砖头,给点启发!
谢谢!
 




  






   发表时间:2008-12-01  
对我帮助很大,慢慢研读~~~
0 请登录后投票
   发表时间:2008-12-12  
  可能篇幅太长,好不容易整的啊,没什么人给意见,伤心下!希望有朋友能一起探讨下!:)  
1 请登录后投票
   发表时间:2008-12-15  
很长时间没碰这个东西了.
有时间再过来看看.
0 请登录后投票
   发表时间:2009-10-21  
不错的文章,不知道使用FieldCache对并发搜索性能提高有多大?希望lz不吝赐教
0 请登录后投票
   发表时间:2009-10-22  
    这个FieldCache 如果用在 filter上面 就让我感觉像 数据库的 index 的原理了,进行快速散列的效果,不过这个没有具体测试,这个适合用在分类字段上面。
0 请登录后投票
   发表时间:2009-11-05  
看Mark Miller的Contrived FieldCache Load Test: Lucene 2.4 VS Lucene 2.9:
http://www.lucidimagination.com/blog/2009/09/22/contrived-fieldcache-load-test-lucene-2-4-vs-lucene-2-9

FieldCache的API:维护term的values的cache。我硬是没有看明白。
    String[] entrys = FieldCache.DEFAULT.getStrings(reader, "field")
返回的是field内容的第一个word的数组。
    int[] size = FieldCache.DEFAULT.getInts(reader, "field");
field的内容必须严格是int字符串,否则异常:java.lang.NumberFormatException。

跟着问一句:“如果有人在fieldcache有不错的用法,请不吝啬手上的砖头,给点启发! ”




0 请登录后投票
   发表时间:2009-11-05  
建议 楼上的朋友再看下帖子和源代码!

FieldCache.DEFAULT 是使用默认实现,其中有多个api,使用的时候最好是针对那些不分词的字段使用,帖子里面有这样的原因说明

引用

int[] size = FieldCache.DEFAULT.getInts(reader, "field");
field的内容必须严格是int字符串,否则异常:java.lang.NumberFormatException。


你调用的是getInts 当然是int数组了,返回的东西是可以通过扩展进行自定义的。

再耐心的看看代码 你肯定会有收获  :〉


0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics