lucene4.7索引源码研究之域元文件

域.fnm的文件结构:

Header,FieldsCount, <FieldName,FieldNumber, FieldBits,DocValuesBits,DocValuesGen,Attributes> FieldsCount,Footer

  • Header: 同前文
  • FieldsCount:域的个数
  • <FieldName,FieldNumber, FieldBits,DocValuesBits,DocValuesGen,Attributes> FieldsCount: 包含FieldsCount个域的信息
  • FieldName:域名
  • FieldNumber:域的编号,不同与先前版本的域的编码是按在文件中的顺序编码,现在版本的域的编码是明确的。
  • FieldBits: 一系列标志位,表明对此域的索引方式
    • 最低位:1表示此域被索引,0则不被索引。所谓被索引,也即放到倒排表中去。

      • 仅仅被索引的域才能够被搜到。
      • Field.Index.NO则表示不被索引。
      • Field.Index.ANALYZED则表示不但被索引,而且被分词,比如索引"hello world"后,无论是搜"hello",还是搜"world"都能够被搜到。
      • Field.Index.NOT_ANALYZED表示虽然被索引,但是不分词,比如索引"hello world"后,仅当搜"hello world"时,能够搜到,搜"hello"和搜"world"都搜不到。
      • 一个域出了能够被索引,还能够被存储,仅仅被存储的域是搜索不到的,但是能通过文档号查到,多用于不想被搜索到,但是在通过其它域能够搜索到的情况下,能够随着文档号返回给用户的域。
      • Field.Store.Yes则表示存储此域,Field.Store.NO则表示不存储此域。
    • 第二最低位:1表示保存词向量,0为不保存词向量。
      • Field.TermVector.YES表示保存词向量。
      • Field.TermVector.NO表示不保存词向量。
    • 第三最低位:1表示在位置列表中保存偏移量信息。
    • 第四最低位:没用
    • 第五最低位:1表示不保存标准化因子
      • Field.Index.ANALYZED_NO_NORMS
      • Field.Index.NOT_ANALYZED_NO_NORMS
    • 第六最低位:是否保存payload
    • 第七最低位:1表示不保存词的频率以及位置信息
    • 第八最低位:不保存位置信息
  • DocValuesBits:一个字节表示DocValues的类型,低4位表示DocValues类型,高4位表示标准类型
  • DocValuesGen:DocValues的版本信息,如果该值为-1表示没有更新DocValues,大于0表示有更新
  • Attributes: 同前文
  • Footer:同前文

要了解域的元数据信息,还要了解以下几点:

  • 位置(Position)和偏移量(Offset)的区别

    • 位置是基于词Term的,偏移量是基于字母或汉字的。

  • 索引域(Indexed)和存储域(Stored)的区别

    • 一个域为什么会被存储(store)而不被索引(Index)呢?在一个文档中的所有信息中,有这样一部分信息,可能不想被索引从而可以搜索到,但是当这个文档由于其他的信息被搜索到时,可以同其他信息一同返回。
    • 举个例子,读研究生时,您好不容易写了一篇论文交给您的导师,您的导师却要他所第一作者而您做第二作者,然而您导师不想别人在论文系统中搜索您的名字时找到这篇论文,于是在论文系统中,把第二作者这个Field的Indexed设为false,这样别人搜索您的名字,永远不知道您写过这篇论文,只有在别人搜索您导师的名字从而找到您的文章时,在一个角落表述着第二作者是您。
  • payload的使用
    • 我们知道,索引是以倒排表形式存储的,对于每一个词,都保存了包含这个词的一个链表,当然为了加快查询速度,此链表多用跳跃表进行存储。
    • Payload信息就是存储在倒排表中的,同文档号一起存放,多用于存储与每篇文档相关的一些信息。当然这部分信息也可以存储域里(stored Field),两者从功能上基本是一样的,然而当要存储的信息很多的时候,存放在倒排表里,利用跳跃表,有利于大大提高搜索速度。
    • Payload的存储方式如下图:

  • Payload主要有以下几种用法:

    • 存储每个文档都有的信息:比如有的时候,我们想给每个文档赋一个我们自己的文档号,而不是用Lucene自己的文档号。于是我们可以声明一个特殊的域(Field)"_ID"和特殊的词(Term)"_ID",使得每篇文档都包含词"_ID",于是在词"_ID"的倒排表里面对于每篇文档又有一项,每一项都有一个payload,于是我们可以在payload里面保存我们自己的文档号。每当我们得到一个Lucene的文档号的时候,就能从跳跃表中查找到我们自己的文档号。
    • 影响词的评分
      • 在Similarity抽象类中有函数public float scorePayload(byte [] payload, int offset, int length)  可以根据payload的值影响评分。

同.si文件相同,.fnm同样有Lucene46FieldInfosFormat(包含Lucene46SegmentInfoReader,Lucene46FieldInfosWriter),以及FieldInfo

看下Lucene46SegmentInfoReader读取.fnm文件部分代码,Lucene46FieldInfosWriter内容差不多就不再介绍。

package org.apache.lucene.codecs.lucene46;

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.io.IOException;
import java.util.Collections;
import java.util.Map;

import org.apache.lucene.codecs.CodecUtil;
import org.apache.lucene.codecs.FieldInfosReader;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.FieldInfo;
import org.apache.lucene.index.FieldInfos;
import org.apache.lucene.index.IndexFileNames;
import org.apache.lucene.index.FieldInfo.DocValuesType;
import org.apache.lucene.index.FieldInfo.IndexOptions;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.IOContext;
import org.apache.lucene.store.IndexInput;
import org.apache.lucene.util.IOUtils;

/**
 * Lucene 4.6 FieldInfos reader.
 *
 * @lucene.experimental
 * @see Lucene46FieldInfosFormat
 */
final class Lucene46FieldInfosReader extends FieldInfosReader {

  /** Sole constructor. */
  public Lucene46FieldInfosReader() {
  }

  @Override
  public FieldInfos read(Directory directory, String segmentName, String segmentSuffix, IOContext context) throws IOException {
    final String fileName = IndexFileNames.segmentFileName(segmentName, segmentSuffix, Lucene46FieldInfosFormat.EXTENSION);
    IndexInput input = directory.openInput(fileName, context);

    boolean success = false;
    try {
      CodecUtil.checkHeader(input, Lucene46FieldInfosFormat.CODEC_NAME,
                                   Lucene46FieldInfosFormat.FORMAT_START,
                                   Lucene46FieldInfosFormat.FORMAT_CURRENT);//检查头信息,包括magic,codecname,version

      final int size = input.readVInt(); //read in the size  域数量
      FieldInfo infos[] = new FieldInfo[size];

      for (int i = 0; i < size; i++) {
        String name = input.readString();//域名称
        final int fieldNumber = input.readVInt();//域的编号
        byte bits = input.readByte();//域标志位,表示该域的特性
  
        boolean isIndexed = (bits & Lucene46FieldInfosFormat.IS_INDEXED) != 0;// bits & 0000 0001 最低位表示是否索引
        boolean storeTermVector = (bits & Lucene46FieldInfosFormat.STORE_TERMVECTOR) != 0;// bits & 0000 0010 倒数第二位表示是否存储词向量
        boolean omitNorms = (bits & Lucene46FieldInfosFormat.OMIT_NORMS) != 0;// bits & 0001 0000 第四位表示是否保存标准化因子
        boolean storePayloads = (bits & Lucene46FieldInfosFormat.STORE_PAYLOADS) != 0;// bits & 0010 0000 第三位表示是否存储payload
        final IndexOptions indexOptions;
        if (!isIndexed) {
          indexOptions = null;
        } else if ((bits & Lucene46FieldInfosFormat.OMIT_TERM_FREQ_AND_POSITIONS) != 0) {// bits & 0100 0000 第二位表示是否保存词频和位置信息 1为不存储          //只索引
          indexOptions = IndexOptions.DOCS_ONLY;
        } else if ((bits & Lucene46FieldInfosFormat.OMIT_POSITIONS) != 0) {// bits & 1000 0000 最高位表是否保存位置信息 1为不存储          //索引和词频
          indexOptions = IndexOptions.DOCS_AND_FREQS;
        } else if ((bits & Lucene46FieldInfosFormat.STORE_OFFSETS_IN_POSTINGS) != 0) {// bits & 0000 0100 是否存储偏移量          //索引、词频、位置、偏移量
          indexOptions = IndexOptions.DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS;
        } else {        //默认情况,索引、词频、位置
          indexOptions = IndexOptions.DOCS_AND_FREQS_AND_POSITIONS;
        }

        // DV Types are packed in one byte        // docValue
        byte val = input.readByte();
        final DocValuesType docValuesType = getDocValuesType(input, (byte) (val & 0x0F));//低4位表示DocValus类型
        final DocValuesType normsType = getDocValuesType(input, (byte) ((val >>> 4) & 0x0F));//高4位表示标准类型
        final long dvGen = input.readLong();//docValues版本号
        final Map<String,String> attributes = input.readStringStringMap();
        infos[i] = new FieldInfo(name, isIndexed, fieldNumber, storeTermVector,
          omitNorms, storePayloads, indexOptions, docValuesType, normsType, Collections.unmodifiableMap(attributes));
        infos[i].setDocValuesGen(dvGen);
      }

      if (input.getFilePointer() != input.length()) {
        throw new CorruptIndexException("did not read all bytes from file \"" + fileName + "\": read " + input.getFilePointer() + " vs size " + input.length() + " (resource: " + input + ")");
      }
      FieldInfos fieldInfos = new FieldInfos(infos);
      success = true;
      return fieldInfos;
    } finally {
      if (success) {
        input.close();
      } else {
        IOUtils.closeWhileHandlingException(input);
      }
    }
  }

  private static DocValuesType getDocValuesType(IndexInput input, byte b) throws IOException {
    if (b == 0) {
      return null;
    } else if (b == 1) {
      return DocValuesType.NUMERIC;
    } else if (b == 2) {
      return DocValuesType.BINARY;
    } else if (b == 3) {
      return DocValuesType.SORTED;
    } else if (b == 4) {
      return DocValuesType.SORTED_SET;
    } else {
      throw new CorruptIndexException("invalid docvalues byte: " + b + " (resource=" + input + ")");
    }
  }
}

(from 追风的蓝宝)

3. DocValue

3.1 Solr中的使用

Solr标准的索引方式是反向索引,它将所有在Document里找到的term放到一起组成一个链表,而每一个term后面又跟着一个term出现过的document的链表以及出现过的次数。在上面的图中显示其原理。这是查询非常迅速,当用户查询某一个term时,已经有准备好的term到document的映射表了。

但是当涉及到sorting(排序), faceting(面搜索), and highlighting(以及高亮)逆向索引就变得不是那么高效了。比如faceting查询,首先得找出每一个document中出现的每一个term,然后使得每一个docID进行排序然后放入faceting list里面。对于Solr来说,这些是在内存中进行的,当document以及term多的时候,就会变得比较慢。

    以facet查询为例:如果商品分类中field:category含有手机phone,照相机camera,电脑computer

统计每个分类下的商品数量,在solr查询中增加请求参数?q=*:*&facet=true&facet.field=category

这个查询的过程是solr的facetcomponent组件会先统计field:category这个field下所有term的在文档中出现的次数

因此在Lucene4.2里引入了DocValue,它是行导向的结构,在建索引的时候形成document到term的映射,它使得faceting, sorting, and grouping 查询更加快速。

要使用它得在schema.xml上设置:

<field name="manu_exact" type="string" indexed="false" stored="false" docValues="true" />

DocValue只对一些特定的类型有效,比如:

  • StrField这种String类型

    • 如果field类型是single-valued(也就是multi-valued是false),Lucene就会使用SORTED类型
    • 如果filed类型是multi-valued,Lucene就会使用SORTED_SET类型
  • Trie* fields 类型
    • 如果field类型是single-valued(也就是multi-valued是false),Lucene就会使用NUMERIC类型
    • 如果filed类型是multi-valued,Lucene就会使用SORTED_SET类型

DocValue具有以下优点以及缺点

  • 近实时索引:在每一个索引段里面都会有一个docvalues数据结构,这个结构与索引同时建立,并且能够快速更新、生效;
  • 基本的查询和过滤支持:你可以做基本的词、范围等基本查询,但是不参与评分,并且速度较慢,如果你对速度和评分排序有要求,你可以讲该字段设置为(indexed=”true”)
  • 更好的压缩比: Docvalues fields 的压缩效果比 fieldcache好,但不强调做到极致。
  • 节约内存:你可以定义一个fieldType的 docValuesFormat (docValuesFormat="Disk"),这样的只有一小部分数据加载到内存,其它部分保留在磁盘上。
  • 对于静态存储的数据查询提升不明显

3.2 DocValue原理

Lucene有四个基础字段类型可以使用docvalues。目前Solr使用了其中三种:

  • NUMERIC:每一个文档里面只有一个这样类型的单值字段。这就像在整个索引里有一个很大的long[],数据基于实际使用的值经过压缩的。

    例如,假设有3个这样的文档:
      doc[0] = 1005
      doc[1] = 1006
      doc[2] = 1005

    在这个例子中,每个文档仅需要一个bit。

  • SORTED:每一个文档里面有一个这样类型的单值字段。这就像在整个索引里有一个很大的String[], 但用的是不同的寻址方式。.每一个唯一的value被赋予一个数字代表其顺序。所以每个文档只是记录一个压缩后的整数,有字典来还原他们原来的词。

    例如,假设有3个这样的文档:
      doc[0] = “aardvark”
      doc[1] = “beaver”
      doc[2] = “aardvark”

    值 “aardvark” 被映射成0,”beaver”映射成1, 建立两个数据结构如下:
      doc[0] = 0
      doc[1] = 1
      doc[2] = 0

      term[0] = “aardvark”
      term[1] = “beaver”

  • SORTED_SET: 每个文档里面有一个string类型的多值字段。这个和SORTED类型比较相似,每个文档有一个value的”set”。(按照递增存储)。 这里刻意的去除了重复的value,并且忽略了原有value的排序。

    例如,假设有3个这样的文档:
      doc[0] = “cat”, “aardvark”, “beaver”, “aardvark”
      doc[1] =
      doc[2] = “cat”

值 “aardvark” 被映射成0,”beaver”映射成1, “cat”映射成2,建立两个数据结构如下:

doc[0] = [0, 1, 2]
doc[1] = []
doc[2] = [2]

term[0] = “aardvark”
term[1] = “beaver”
term[2] = “cat”

    • BINARY: 每个文档存在一个 byte[] array。这个编码及数据结构可以由用户自定义。
时间: 2024-08-02 15:13:36

lucene4.7索引源码研究之域元文件的相关文章

Chrome自带恐龙小游戏的源码研究(完)

在上一篇<Chrome自带恐龙小游戏的源码研究(七)>中研究了恐龙与障碍物的碰撞检测,这一篇主要研究组成游戏的其它要素. 游戏分数记录 如图所示,分数及最高分记录显示在游戏界面的右上角,每达到100分就会出现闪烁特效,游戏第一次gameover时显示历史最高分.分数记录器由DistanceMeter构造函数实现,以下是它的全部代码: 1 DistanceMeter.dimensions = { 2 WIDTH: 10, //每个字符的宽度 3 HEIGHT: 13, //每个字符的高 4 DE

Chrome自带恐龙小游戏的源码研究(七)

在上一篇<Chrome自带恐龙小游戏的源码研究(六)>中研究了恐龙的跳跃过程,这一篇研究恐龙与障碍物之间的碰撞检测. 碰撞盒子 游戏中采用的是矩形(非旋转矩形)碰撞.这类碰撞优点是计算比较简单,缺点是对不规则物体的检测不够精确.如果不做更为精细的处理,结果会像下图: 如图所示,两个盒子虽然有重叠部分,但实际情况是恐龙和仙人掌之间并未发生碰撞.为了解决这个问题,需要建立多个碰撞盒子: 不过这样还是有问题,观察图片,恐龙和仙人掌都有四个碰撞盒子,如果每次Game Loop里都对这些盒子进行碰撞检测

Redis源码研究—哈希表

Redis源码研究-哈希表 Category: NoSQL数据库 View: 10,980 Author: Dong 作者:Dong | 新浪微博:西成懂 | 可以转载, 但必须以超链接形式标明文章原始出处和作者信息及版权声明 网址:http://dongxicheng.org/nosql/redis-code-hashtable/ 本博客的文章集合:http://dongxicheng.org/recommend/ 本博客微信公共账号:hadoop123(微信号为:hadoop-123),分享

Chrome自带恐龙小游戏的源码研究(五)

在上一篇<Chrome自带恐龙小游戏的源码研究(四)>中实现了障碍物的绘制及移动,从这一篇开始主要研究恐龙的绘制及一系列键盘动作的实现. 会眨眼睛的恐龙 在游戏开始前的待机界面,如果仔细观察会发现恐龙会时不时地眨眼睛.这是通过交替绘制这两个图像实现的: 可以通过一张图片来了解这个过程: 为实现图片的切换,需要一个计时器timer,并且需要知道两张图片切换的时间间隔msPerFrame.当计时器timer的时间大于切换的时间间隔msPerFrame时,将图片切换到下一张,到达最后一张时又从第一张

live555源码研究(四)------UserAuthenticationDatabase类

一.UserAuthenticationDatabase类作用 1,用户/密码管理 2,鉴权管理 二.类UserAuthenticationDatabase继承关系图                         live555源码研究(四)------UserAuthenticationDatabase类,布布扣,bubuko.com

Chrome自带恐龙小游戏的源码研究(六)

在上一篇<Chrome自带恐龙小游戏的源码研究(五)>中实现了眨眼睛的恐龙,这一篇主要研究恐龙的跳跃. 恐龙的跳跃 游戏通过敲击键盘的Spacebar或者Up来实现恐龙的跳跃.先用一张图来表示整个跳跃的过程: 首先规定向下为正方向,即重力加速度(g)为正,起跳的速度(v)为负,恐龙距离画布上方的距离为yPos: 每一帧动画中,速度都会与重力加速度相加得到新的速度,再用新的速度与yPos相加得到新的yPos,改变恐龙的位置为新的yPos,表现出来为yPos不断减小: 当恐龙升至最高点,此时速度为

Mina源码研究

目录 1. NioSocketAcceptor初始化源码研究 1.1 类图 1.2 方法调用时序图 1.3 初始化NioSocketAcceptor 1.4 SimpleIoProcessorPool初始化分析 1.5 NioProcessor的源码 1.6 总结 2. NioSocketAcceptor bind方法源码研究 2.1 创建ServerSocket监听 2.1.1 时序图 2.1.2 bind方法 2.1.3 startupAcceptor方法 2.1.4 创建ServerSoc

LIVE555源码研究之四:MediaServer (一)

从本篇文章开始我们将从简单服务器程序作为突破点,深入研究LIVE555源码. 从前面的文章我们知道,任何一个基于LIVE555库实现的程序都需要实现自己的环境类和调度类.这里,服务器程序就使用了BasicEnvironment库中实现的简单环境类和简单调度类.说它简单,是因为该环境类仅仅实现了将错误信息输出到控制台.而调度类仅仅通过select模型实现socket的读写. 下面我们来看下简单环境类BasicEnvironment和简单调度类BasicTaskScheduler是如何实现的. 打开

live555源码研究(五)------DynamicRTSPServer类

一.类DynamicRTSPServer作用 1,提供RTSP服务 二.类DynamicRTSPServer继承关系图 live555源码研究(五)------DynamicRTSPServer类,布布扣,bubuko.com