您的位置:首页 > 其它

lucene FieldCache 内部细节和使用方式分享

2016-04-21 10:36 260 查看
以下主要以使用的lucene 3.5为例:
 
一,FieldCache是什么?
FieldCache其实就是一个字段域值的一个缓存,外层是一些Map,
它的底层数据结构就是一个数组,数组的下标就是docId,
值就是docId对应的Field value.
可以快速完成docId到Field value的映射。
 
二,FieldCache怎么使用以及使用场景是什么?
    1)使用方式:
比如Field的数据类型为Int,那么就可以通过如下方式来使用:
int[] fieldValues=FieldCache.DEFAULT.getInts(IndexReader reader, String field);
或者
int[] fieldValues=FieldCache.DEFAULT.getInts(IndexReader reader, String field, IntParser parser)
或者
int[] fieldValues=FieldCache.DEFAULT.getInts(IndexReader reader, String field, IntParser parser, boolean setDocsWithField)
第一个参数就是IndexReader,第二个参数就是field的名称,第三个参数parser,它可以把值转换成对于的数据类型,第四个参数setDocsWithField是否记录存在这个Field的docID。
2)使用场景:
   其实使用场景有很多,在我们的代码里可以随时使用,这里举例几种比较典型的使用场景
   1,用SortField排序时,Lunene会使用这个字段对应的FieldCache
   2,solr
在做facet时也会使用
   3,在自定义的Collector里,使用详情可以参考mandy内部的YHDCollector
 
三,FieldCache的内部实现细节
1,从结构层次上看,FieldCacheImpl是一个三层的Map实现。
   a , 第一层Map是一个HashMap,根据数据类型来划分,有9种类型的cache,根据字段类型不同选择不同的cache.
 Key为数据类型,value为Cache类的子类实例, Cache类里包含了第二层的Map。
   private synchronized void init() {
      caches = new HashMap<Class<?>,Cache>(9);
      caches.put(Byte.TYPE, new ByteCache(this));
      caches.put(Short.TYPE, new ShortCache(this));
      caches.put(Integer.TYPE, new IntCache(this));
      caches.put(Float.TYPE, new FloatCache(this));
      caches.put(Long.TYPE, new LongCache(this));
      caches.put(Double.TYPE, new DoubleCache(this));
      caches.put(String.class, new StringCache(this));
      caches.put(StringIndex.class, new StringIndexCache(this));
      caches.put(DocsWithFieldCache.class, new DocsWithFieldCache(this));
    }
b, 第二层的Map是一个WeakHashMap,按照IndexReader来划分,不同的IndexReader实例对应不同的cache,,key为IndexReader实例,
value为第三层的map。
 
c,第三层的Map是一个HashMap,
按照字段来划分,以Entry为key, Entry由FieldName和parser组成,value就是一个数组或者FixedBitSet。
    
2, 解析实现细节:
1)先来看一下Cache类的定义,主要包含这几个部分:
  A, final Map<Object,Map<Entry,Object>>
readerCache = new WeakHashMap<Object,Map<Entry,Object>>();
内部定义一个WeakHashMap,这个作为第二层的map,以IndexReader为key,以第三层的hashMap作为value.
             B,protected
abstract Object createValue(IndexReader reader, Entry key,
boolean setDocsWithField)
        throws IOException;
                一个抽象方法,9种不同数据类型的子类cache都必须实现这个方法,用于获取真正的Field value,来填充字段对应的数组或者FixedBitSet.
    C, put(IndexReader reader, Entry key, Object value)
       用于构建整个Cache体系,创建第三层的innerCache,然后把参数key和<
d3f3
/span>value放入innerCache中,最后把参数reader和innerCache放入到第二层的weakHashMap中
             D,get(IndexReader reader, Entry key, boolean setDocsWithField)
                 根据reader和Entry(entry由fieldName和parser组成)来获取field对应的数组或者FixedBitSet,
如果不存在,通过调用子类的实现的createValue
方法获取,并放入各层map中。setDocsWithField是否记录存在这个Field的docID集合
           E,purge(IndexReader r) 
如果IndexReader已经完成或者关闭了,就会调用该方法,用于清理weakHashMap中IndexReader对应的value
        
2) 看一下Cache抽象方法createValue(IndexReader reader, Entry key,
boolean setDocsWithField)在子类中的实现,以子类IntCache
为例:
            final
int maxDoc = reader.maxDoc(); //
获取reader对应的maxDoc,这个用于定义数组的长度或者FixedBitSet的大小
              TermDocs termDocs = reader.termDocs();//获取reader对应termDocs,不断循环迭代获取所有的docId
              TermEnum termEnum = reader.terms (new Term (field));//获取reader对应的TermEnum,不断循环迭代获取所有的field的值
              通过不断的循环迭代,构建出最后填充好的数组或者FixedBitSet,完成docId到Field value的映射.
         
3) 看一下parser, 如果是一个数字类型的Field,使用时要注意,举例来说,  
如果采用老的方式,doc.add(new Field("stock_"+i,, Integer.toString(product.getStock()), Field.Store.NO, Field.Index.NOT_ANALYZED_NO_NORMS));
加载FieldCache时就用DEFAULT_INT_PARSER,inBrandsValues = FieldCache.DEFAULT.getInts(reader, "brand_id",FieldCache.DEFAULT_INT_PARSER,
missingValue != null);
如果是用doc.add(new NumericField("stock_"+i, Field.Store.NO, true).setIntValue(0));  加载时FieldCache如果使用DEFAULT_INT_PARSER就会报错,
必须使用FieldCache.NUMERIC_UTILS_INT_PARSER,因为NumericField 在索引的时候是经过特殊编码的。或者直接使用FieldCache.DEFAULT.getInts(reader,
"brand_id"),不加parser.
原因如下,在createValue方法中有如下代码,如果没有传入parser,
那么默认采用DEFAULT_INT_PARSER,发生NumberFormatException异常后会采用NUMERIC_UTILS_INT_PARSER.
               if (parser ==
null) {
                   try {
                      return
wrapper.getInts(reader, field,
DEFAULT_INT_PARSER, setDocsWithField);
                   } catch (NumberFormatException ne) {
                      return
wrapper.getInts(reader, field,
NUMERIC_UTILS_INT_PARSER, setDocsWithField);
                   }
}
 
建议我们在加载FieldCache时,可以直接使用FieldCache.DEFAULT.getInts(reader, "brand_id")方式,不要传任何的parser,肯定不会报错。
 
4) 第二层Map是一个的WeakHashMap,内部实现采用的是WeakReference,
这并不会阻止垃圾回收器对其内部数据的回收,垃圾回收器可能在任何时候对其数据进行回收,如果回收掉了,
下次再使用时就必须重新加载,Lucene采用WeakHashMap也是考虑到内存占用的问题。其实FieldCache加载时需要遍历所有的doc,有一定的性能消耗,需要做一个权衡。
 
        5) 再来看一下FixedBitSet
          FixedBitSet是一个固定长度为reader.maxDoc的BitSet,非常的节省空间,相当于同样大小的int数组的1/32,
上面第9种cache是DocsWithFieldCache,内部就是使用了fixedBitSet,
在有两种情况可以使用:
          1,如果我们并不需要直接使用和获取FieldCache中Field value,只是需要判断doc是否存在这个字段时,可以采用FixedBitSet.
             public Bits getDocsWithField(IndexReader reader, String field)
             例如判断有无库存时,是可以采用这种方式的。
          2,在使用SortField排序时,如果设置了missingValue,
内部实现也是一个FixedBitSet.
SortField stockSort = new SortField("stock_" +
req.getProvinceId(),SortField.INT,
true).setMissingValue(1)
意思是如果doc没有这个字段,采用默认值1参与排序。
 
四,使用过程中注意点:
1,FieldCache的字段必须是被索引的,是否保存没有关系。
2,FieldCache的字段必须是单值域,也就意味着创建Field必须选择Field.Index.NOT_ANALYZED或者Field.Index.NOT_ANALYZED_NO_NORMS
    如果选择的是Field.Index.ANALYZED或者Field.Index.ANALYZED_NO_NORMS,这个Field value有可能被分词成多个Term,
     FieldCache加载时只会加载每个Field value分词之后的最后那个Term,
这个时候加载的数据是不正确的。有的Field虽然不可能被分词,但是尽量还是不要选择
     Field.Index.ANALYZED或者Field.Index.ANALYZED_NO_NORMS。
3,如果字段是存在多个同名域,加载FieldCache也会不正确。
     doc.add(new Field("id", String.valueOf(i), Field.Store.NO, Field.Index.NOT_ANALYZED_NO_NORMS));
    doc.add(new Field("id", String.valueOf(i+1), Field.Store.NO, Field.Index.NOT_ANALYZED_NO_NORMS));
FieldCache加载的是同名域的后面那个Field的value.
4,lucene4.2
以下版本加载FieldCache时,尽量避免传入不存在的FieldName,
因为即使传入一个不存在的Field,也会创建一个数组,占用同样多的内存。
5,调用FieldCache,不要传入最外层的IndexReader,最好传入都是SegmentReader,如果既有外层的IndexReader被传入,又有内层的SegmentReader被传入,相当于占用了两份内存。
   1)在Collector和FieldComparator中类存在下面的方法,这个方法里的参数是SegmentReader,可以在这个方法里加载需要的FieldCache.
setNextReader(IndexReader reader, int docBase)
   2) 如果拿到的是外层的IndexReader的话,可以先获取内部的subReaders,
然后判断是否为SegmentReader,直到获取到SegmentReader,再加载FieldCache.
 
 
Lucene4.2及以上版本,FieldCache实现大体是一样的,增加了从docValues中获取FieldCache,对不存在的FieldName做了特殊处理等等!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  lucene fieldcache 搜索