前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Lucene系列(七)索引格式之fdx文件

Lucene系列(七)索引格式之fdx文件

作者头像
呼延十
发布2021-02-02 14:36:27
6350
发布2021-02-02 14:36:27
举报
文章被收录于专栏:呼延呼延

前言

这篇文章介绍.fdx文件格式.

.fdx文件整体格式

2021-01-28-19-21-27
2021-01-28-19-21-27

看起来比较简单, 实际写入代码是fdt,fdm,fdx三个文件中最复杂的.

其中内容包括:

  1. IndexHeader. 索引文件头,前面说过,就不细说了.
  2. Footer: 索引文件脚, 不细说.
  3. ChunkDocsNum: 一个数组,含义是: 每个Chunk中的doc数量.
  4. ChunkStartPoint: 一个数组,含义是: 每个chunk的内容在fdt文件中文件地址.

鉴于存储方式比较复杂, 我们就直接快进到源代码.

写入代码分析

CompressingStoredFieldsWriter类的构造函数中, 初始化了FieldsIndexWriter类的实例, 由它来进行fdx文件的写入,看看他的构造函数.

代码语言:javascript
复制
  FieldsIndexWriter(Directory dir, String name, String suffix, String extension,
                    String codecName, byte[] id, int blockShift, IOContext ioContext) throws IOException {
    this.dir = dir;
    this.name = name;
    this.suffix = suffix;
    this.extension = extension;
    this.codecName = codecName;
    this.id = id;
    this.blockShift = blockShift;
    this.ioContext = ioContext;
    // docNum 的tmp文件
    this.docsOut = dir.createTempOutput(name, codecName + "-doc_ids", ioContext);
    boolean success = false;
    try {
      CodecUtil.writeHeader(docsOut, codecName + "Docs", VERSION_CURRENT);

      // StartPoint 的tmp文件
      filePointersOut = dir.createTempOutput(name, codecName + "file_pointers", ioContext);
      CodecUtil.writeHeader(filePointersOut, codecName + "FilePointers", VERSION_CURRENT);
      success = true;
    } finally {
      if (success == false) {
        close();
      }
    }
  }

在构造函数中, 没有创建fdx文件,而是创建了两个临时文件, docsOutfilePointOut. 分别用于存储前面提到的两份数据. 每个Chunk中的doc数量每个chunk的内容在fdt文件中文件地址.

之后,每次向fdt文件中,写入一个chunk的内容, 同时会调用下方的方法, 写入当前chunk的doc数量,及fdt文件地址. 注意写入的是临时文件.

代码语言:javascript
复制
  void writeIndex(int numDocs, long startPointer) throws IOException {
    assert startPointer >= previousFP;
    // doc num
    docsOut.writeVInt(numDocs);
    // filepoint
    filePointersOut.writeVLong(startPointer - previousFP);
    previousFP = startPointer;
    totalDocs += numDocs;
    totalChunks++;
  }

在所有数据写入完成后, 会调用FieldsIndexWriter类的finish方法,来进行生成真正的fdx文件. 该方法比较复杂, 让我们一步步捋一下.

代码语言:javascript
复制
  /**
   * 在这里生成的fdx文件,从两个tmp文件里面找到每个chunk的doc数量,fdt文件中存储的字节数,
   * 这两个内容,写到meta文件和fdx文件中,配合起来存储的
   * <p>
   * 这个类本身就是为了fdx文件搞的,就是为了写fdt的索引,写得少很正常
   */
  void finish(int numDocs, long maxPointer, IndexOutput metaOut) throws IOException {
    if (numDocs != totalDocs) {
      throw new IllegalStateException("Expected " + numDocs + " docs, but got " + totalDocs);
    }
    CodecUtil.writeFooter(docsOut);
    CodecUtil.writeFooter(filePointersOut);
    IOUtils.close(docsOut, filePointersOut);

    // dataOut 是fdx文件,是用来对fdt文件做索引的文件,所以fdt文件写入内容,我这里记录每个chunk的doc数量,占用字节数即可
    // 所以这里只能调用一次么,无论是多少个多大的field,都只能调用一次这里么
    // 写fdx文件
    try (IndexOutput dataOut = dir.createOutput(IndexFileNames.segmentFileName(name, suffix, extension), ioContext)) {
      // 这个header,48个字节.
      CodecUtil.writeIndexHeader(dataOut, codecName + "Idx", VERSION_CURRENT, id, suffix);

      metaOut.writeInt(numDocs);
      metaOut.writeInt(blockShift);
      metaOut.writeInt(totalChunks + 1);
      // 这个filePointer,此时只写了一个header的长度,48
      long filePointer = dataOut.getFilePointer();
      metaOut.writeLong(filePointer);

      try (ChecksumIndexInput docsIn = dir.openChecksumInput(docsOut.getName(), IOContext.READONCE)) {
        CodecUtil.checkHeader(docsIn, codecName + "Docs", VERSION_CURRENT, VERSION_CURRENT);
        Throwable priorE = null;
        try {
          // 这里做的配合是, meta里面存了min/斜率等,真实的数组偏移量在dataOut里面存储
          final DirectMonotonicWriter docs = DirectMonotonicWriter.getInstance(metaOut, dataOut, totalChunks + 1, blockShift);
          long doc = 0;
          docs.add(doc);
          // 注意,这里是每一chunk, 而不是per document
          for (int i = 0; i < totalChunks; ++i) {
            // 每个chunk的doc数量
            doc += docsIn.readVInt();
            docs.add(doc);
          }
          docs.finish();
          if (doc != totalDocs) {
            throw new CorruptIndexException("Docs don't add up", docsIn);
          }
        } catch (Throwable e) {
          priorE = e;
        } finally {
          CodecUtil.checkFooter(docsIn, priorE);
        }
      }
      dir.deleteFile(docsOut.getName());
      docsOut = null;

      long filePointer1 = dataOut.getFilePointer();
      metaOut.writeLong(filePointer1);
      try (ChecksumIndexInput filePointersIn = dir.openChecksumInput(filePointersOut.getName(), IOContext.READONCE)) {
        CodecUtil.checkHeader(filePointersIn, codecName + "FilePointers", VERSION_CURRENT, VERSION_CURRENT);
        Throwable priorE = null;
        try {
          // 其实由于我测试的时候只有一两个doc,肯定在一个chunk,所以dataOut里面都没写入啥东西
          final DirectMonotonicWriter filePointers = DirectMonotonicWriter.getInstance(metaOut, dataOut, totalChunks + 1, blockShift);
          long fp = 0;
          // 这里存储的是每一个chunk的实际数据的字节长度
          for (int i = 0; i < totalChunks; ++i) {
            fp += filePointersIn.readVLong();
            filePointers.add(fp);
          }
          if (maxPointer < fp) {
            throw new CorruptIndexException("File pointers don't add up", filePointersIn);
          }
          filePointers.add(maxPointer);
          filePointers.finish();
        } catch (Throwable e) {
          priorE = e;
        } finally {
          CodecUtil.checkFooter(filePointersIn, priorE);
        }
      }
      dir.deleteFile(filePointersOut.getName());
      filePointersOut = null;

      // meta里面再搞个索引
      long filePointer2 = dataOut.getFilePointer();
      metaOut.writeLong(filePointer2);
      metaOut.writeLong(maxPointer);

      CodecUtil.writeFooter(dataOut);
    }
  }

需要注意, 此时所有的field数据已经写入. 进行文件的转换操作而已.

  1. 向两个临时文件写入Footer, 之后将其关闭.
  2. 打开真正的fdx文件,写入Header.
  3. 向之前介绍过的fdm文件中,写入部分元数据.不是这篇文章重点,就不详细解释了.
  4. 打开刚才的临时文件DocsOut, 把数据读出来. 使用DirectMonotonicWriter来将数据写入fdx文件. 对DirectMonotonicWriter类不熟悉的话, 可以阅读 DirectMonotonicWriter源码解析. 之后将Docs的临时文件删除.
  5. 打开刚才的临时文件filePointOut, 把数据读出来, 调用DirectMonotonicWriter进行写入fdx文件. 之后将临时文件删除.
  6. 向fdx文件写入Footer. 关闭文件.

如何索引?

从名字上可以看出来, fdx文件是用来作为fdt文件的索引的. 作用就是: 能够方便快速查询到指定的doc的field信息.

那么它是如何作为索引的呢, 三个field相关文件的对应关系是怎样的.

以下内容为猜想内容, 如果你看到这条红字, 不要相信. 未来的某一天,我看到代码且确认了下面的内容, 我会回来删掉这行红字.

2021-01-28-20-20-25
2021-01-28-20-20-25

当我们拿到一个DocId, 该如何通过这三个文件拿到该doc的具体field信息呢?

首先, fdx及fdm文件都比较小,可以全部加载到内存中.

  1. 根据fdm中的ChunkDocsNumIndex, 可以找到在fdx文件中, 存储Chunk中doc数量的起始文件地址.
  2. 读出每个Chunk的doc数量, 用docId, 即可以算出 该DocId位于第几个Chunk的第几个Doc.
  3. 根据fdx文件中ChunkDocsNum 和 ChunkStartPoint 文件时平行数据的关系, 即可以求出, DocId所在的chunk, 其field信息在fdt文件中的起始文件位置.
  4. 将fdt文件中, 该chunk的数据读入, 即可获取到给定DocId的具体内容.

不用完整的遍历fdt文件,而是通过fdx及fdm做了一些索引操作. 比较高效.

总结

fdx文件中, 主要是存储以chunk为单位的doc数量, 对应chunk在fdt文件中的起始位置. 由这些数据可以对fdt文件进行随机方法而不用顺序访问,加快了读取速度.

为了对fdx文件中的数据进行压缩, 防止读取到内存中过大,需要fdm进行一些配合存储. 通过DirectMonotonicWriter进行压缩写入.

完。

以上皆为个人所思所得,如有错误欢迎评论区指正。

欢迎转载,烦请署名并保留原文链接。

联系邮箱:huyanshi2580@gmail.com

更多学习笔记见个人博客或关注微信公众号 <呼延十 >——>呼延十

var gitment = new Gitment({ id: 'Lucene系列(七)索引格式之fdx文件', // 可选。默认为 location.href owner: 'hublanker', repo: 'blog', oauth: { client_id: '2297651c181f632a31db', client_secret: 'a62f60d8da404586acc965a2ba6a6da9f053703b', }, }) gitment.render('container')



本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • .fdx文件整体格式
  • 写入代码分析
  • 如何索引?
  • 总结
相关产品与服务
腾讯云代码分析
腾讯云代码分析(内部代号CodeDog)是集众多代码分析工具的云原生、分布式、高性能的代码综合分析跟踪管理平台,其主要功能是持续跟踪分析代码,观测项目代码质量,支撑团队传承代码文化。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档