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做了特殊处理等等!
一,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做了特殊处理等等!
相关文章推荐
- 搜狗百度360市值齐跌:搜索引擎们陷入集体焦虑?
- 本人即将筹备败家日志,敬请期待!
- IE:使用搜索助手
- C++深度优先搜索的实现方法
- 基于文本的搜索
- php实现搜索一维数组元素并删除二维数组对应元素的方法
- 使用Sphinx对索引进行搜索
- asp 多关键词搜索的简单实现方法
- C#使用foreach语句搜索数组元素的方法
- WordPress中用于获取搜索表单的PHP函数使用解析
- JavaScript中数组的排序、乱序和搜索实现代码
- jquery ztree实现树的搜索功能
- C#编程实现Excel文档中搜索文本内容的方法及思路
- sqlserver中在指定数据库的所有表的所有列中搜索给定的值
- 可以用来搜索当前页面内容的js代码
- 全文搜索和替换
- javascript搜索自动提示功能的实现第1/3页
- java Lucene 中自定义排序的实现
- iOS应用中UISearchDisplayController搜索效果的用法
- mysql 模糊搜索的方法介绍