ORC源码阅读(2) - OrcProto

本文详细解析了ORC文件的内部结构,包括FileTail、Stripe、Index等关键组成部分及其编码方式,有助于理解ORC文件如何高效存储和检索数据。

读了mapreduce module之后,对orc的代码有了大概的了解,其实顺着RecordReader、Write和OrcFile、OrcStruct往下读就会有很多和protocol buffers有关的东西。其实如果对orc没有基本了解的话,应该先结合orc的文档读一下proto文件,这个文件在orc源码的proto目录下,文件名为orc_proto.proto。

orc使用protocol buffers来定义一个orc file中元数据的结构。在编译orc源码的时候,会根据proto文件中的message定义生成相应的类,以及类序列化、反序列化的java代码。生成的代码默认在java/core/target/generated_sources/org/apache/orc/OrcProto.java中。但是在实际读写orc file的时候,序列化和反序列化并不完全依赖与生成的代码,比如写FileTail的时候,按照proto中的定义,应该是先写postscript再写footer,而实际上在orc的org.apache.orc.impl.WriterImpl中,是先写footer再写postscript的。

Documentation

首先看一下文档中的文件格式相关的重点:

Type

https://orc.apache.org/docs/types.html
All ORC file are logically sequences of identically typed objects. Hive always uses a struct with a field for each of the top-level columns as the root object type, but that is not required. All types in ORC can take null values including the compound types.

Index

https://orc.apache.org/docs/indexes.html
https://orc.apache.org/docs/spec-index.html
这两个全程重点

Introduction

https://orc.apache.org/docs/spec-intro.html
全程重点

FileTail

https://orc.apache.org/docs/file-tail.html
全程重点

Compression

https://orc.apache.org/docs/compression.html
随便看看

Stripe

https://orc.apache.org/docs/stripes.html
全程重点

ColumnEncoding

https://orc.apache.org/docs/encodings.html
全程重点,这个对理解proto定义很关键
https://orc.apache.org/docs/run-length.html
随便看看,介绍了数值型的列怎么做RLE。

然后开始读proto文件。
这个文件可以从前往后大概读一遍,然后从后往前细细的读。我们就从后往前读。

FileTail

FileTail是一个Orc文件的尾部。对应于Introduction的图中文件结构最后的那一小块。定义如下:

message FileTail {
  optional PostScript postscript = 1;
  optional Footer footer = 2;
  optional uint64 fileLength = 3;
  optional uint64 postscriptLength = 4;
}

这个FileTail的定义其实是有问题的,和文档不一致(https://orc.apache.org/docs/spec-intro.htmlhttps://orc.apache.org/docs/file-tail.html),而文档中的部分内容又和以及实际的orc file中的内容不一致。感觉这是有意的,不知道为啥要这样,可能就是因为懒得弄或者作者的某种癖好吧。一切以实际的orc file中写入的内容为准,在WriterImpl中可以看到:

  private long writeFooter() throws IOException {
    writeMetadata();
    OrcProto.Footer.Builder builder = OrcProto.Footer.newBuilder();
    builder.setNumberOfRows(rowCount);
    builder.setRowIndexStride(rowIndexStride);
    rawDataSize = computeRawDataSize();
    // serialize the types
    writeTypes(builder, schema);
    // add the stripe information
    for(OrcProto.StripeInformation stripe: stripes) {
      builder.addStripes(stripe);
    }
    // add the column statistics
    writeFileStatistics(builder, treeWriter);
    // add all of the user metadata
    for(Map.Entry<String, ByteString> entry: userMetadata.entrySet()) {
      builder.addMetadata(OrcProto.UserMetadataItem.newBuilder()
        .setName(entry.getKey()).setValue(entry.getValue()));
    }
    builder.setWriter(OrcFile.WriterImplementation.ORC_JAVA.getId());
    physicalWriter.writeFileFooter(builder);
    return writePostScript();
  }

在写文件的footer的writeFooter方法中,最开始就写入了metadata,最后写入了postscript。
可见:
1. FileTail里面实际上是有Metadata的;
2. 虽然postscript的id是1,但是实际上在序列化的时候,会先序列化footer再序列化postscript
实际上,在读取一个orc file的时候,会先从文件的末尾读取16KB,并从最后一个字节中取出postscript的长度(虽然postscriptLength是64位,但是实际上postscript长度不超过255个字节,所以只用了一个字节)。

PostScript

定义如下:

message PostScript {
  optional uint64 footerLength = 1;
  optional CompressionKind compression = 2;
  optional uint64 compressionBlockSize = 3;
  repeated uint32 version = 4 [packed = true];
  optional uint64 metadataLength = 5;
  optional uint32 writerVersion = 6;
  optional string magic = 8000;
}

从postscript中就知道footer的长度、压缩算法、元数据长度,然后就可以计算出footer和metadata所在的偏移。

定义如下:

message Footer {
  optional uint64 headerLength = 1;
  optional uint64 contentLength = 2;
  repeated StripeInformation stripes = 3;
  repeated Type types = 4;
  repeated UserMetadataItem metadata = 5;
  optional uint64 numberOfRows = 6;
  repeated ColumnStatistics statistics = 7;
  optional uint32 rowIndexStride = 8;

  // Each implementation that writes ORC files should register for a code
  // 0 = ORC Java
  // 1 = ORC C++
  // 2 = Presto
  optional uint32 writer = 9;
}

headerLength永远是3,orc file会在文件开头存’ORC’3个字符,占用3个字节,这个叫做header,用来告诉其他不明所以的file reader这是个ORC文件,不要乱读。其他字段的解释看文档https://orc.apache.org/docs/file-tail.html.

其中stripes就是各个stripe的元数据,types是文件的schema。rowIndexStride是个配置参数,指一个index entry中最大的行数。这个index entry就是指proto中的RowIndexEntry,一个stripe中有很多column,每个column上的每10000行为一个row group,一个row group上面有一个RowIndexEntry。

stripes就是存了各个stripe的偏移和stripe中index、data、footer等部分的length。定义如下:

message StripeInformation {
  optional uint64 offset = 1;
  optional uint64 indexLength = 2;
  optional uint64 dataLength = 3;
  optional uint64 footerLength = 4;
  optional uint64 numberOfRows = 5;
}

types是个很有意思的结构,目测(我也不是非常确定)types对应于https://orc.apache.org/docs/types.html 最后那个图里面的那棵树上的各个节点,节点上的id就是在types数组中的下标。可以看一下Type的定义:

message Type {
  enum Kind {
    BOOLEAN = 0;
    BYTE = 1;
    SHORT = 2;
    INT = 3;
    LONG = 4;
    FLOAT = 5;
    DOUBLE = 6;
    STRING = 7;
    BINARY = 8;
    TIMESTAMP = 9;
    LIST = 10;
    MAP = 11;
    STRUCT = 12;
    UNION = 13;
    DECIMAL = 14;
    DATE = 15;
    VARCHAR = 16;
    CHAR = 17;
  }
  optional Kind kind = 1;
  repeated uint32 subtypes = 2 [packed=true];
  repeated string fieldNames = 3;
  optional uint32 maximumLength = 4;
  optional uint32 precision = 5;
  optional uint32 scale = 6;
}

其中的subtypes和fieldNames应该分别是该节点的孩子节点在types的下标和名称。如果是这样,那么types就是一颗以数组形式存储的树,这样存确实非常方便也比较省空间。

Metadata

Metadata分为UserMetadata和Metadata。UserMetadata是作为footer一部分存储的,在上面的footer定义中可以看到。定义如下:

message UserMetadataItem {
  optional string name = 1;
  optional bytes value = 2;
}

就是一个key-value的形式,没啥好说的,用来让用户自定义些什么东西放在footer里面。
Metadata的定义如下:

message Metadata {
  repeated StripeStatistics stripeStats = 1;
}

message StripeStatistics {
  repeated ColumnStatistics colStats = 1;
}

message ColumnStatistics {
  optional uint64 numberOfValues = 1;
  optional IntegerStatistics intStatistics = 2;
  optional DoubleStatistics doubleStatistics = 3;
  optional StringStatistics stringStatistics = 4;
  optional BucketStatistics bucketStatistics = 5;
  optional DecimalStatistics decimalStatistics = 6;
  optional DateStatistics dateStatistics = 7;
  optional BinaryStatistics binaryStatistics = 8;
  optional TimestampStatistics timestampStatistics = 9;
  optional bool hasNull = 10;
}

message IntegerStatistics  {
  optional sint64 minimum = 1;
  optional sint64 maximum = 2;
  optional sint64 sum = 3;
}

message DoubleStatistics {
  optional double minimum = 1;
  optional double maximum = 2;
  optional double sum = 3;
}

message StringStatistics {
  optional string minimum = 1;
  optional string maximum = 2;
  // sum will store the total length of all strings in a stripe
  optional sint64 sum = 3;
}

message BucketStatistics {
  repeated uint64 count = 1 [packed=true];
}

message DecimalStatistics {
  optional string minimum = 1;
  optional string maximum = 2;
  optional string sum = 3;
}

message DateStatistics {
  // min,max values saved as days since epoch
  optional sint32 minimum = 1;
  optional sint32 maximum = 2;
}

message TimestampStatistics {
  // min,max values saved as milliseconds since epoch
  optional sint64 minimum = 1;
  optional sint64 maximum = 2;
  optional sint64 minimumUtc = 3;
  optional sint64 maximumUtc = 4;
}

message BinaryStatistics {
  // sum will store the total binary blob length in a stripe
  optional sint64 sum = 1;
}

虽然长了点,但是很清晰,就是存了一个orc file中各个stripe中的各个column上的统计信息,如最大值、最小值等等。

FileTail这样就算是基本捋清楚了。

Stripe

一个Orc file除了末尾的file tail和开头的三字节header这两部分,就是file tail和header之间的各个stripe。
https://orc.apache.org/docs/spec-intro.html 所述,每个stripe有index、data、footer三个部分。注意这里的footer是stripe footer,不是file footer。

首先看footer定义如下:

message StripeFooter {
  repeated Stream streams = 1;
  repeated ColumnEncoding columns = 2;
  optional string writerTimezone = 3;
}

message Stream {
  // if you add new index stream kinds, you need to make sure to update
  // StreamName to ensure it is added to the stripe in the right area
  enum Kind {
    PRESENT = 0;
    DATA = 1;
    LENGTH = 2;
    DICTIONARY_DATA = 3;
    DICTIONARY_COUNT = 4;
    SECONDARY = 5;
    ROW_INDEX = 6;
    BLOOM_FILTER = 7;
    BLOOM_FILTER_UTF8 = 8;
  }
  optional Kind kind = 1;
  optional uint32 column = 2;
  optional uint64 length = 3;
}

message ColumnEncoding {
  enum Kind {
    DIRECT = 0;
    DICTIONARY = 1;
    DIRECT_V2 = 2;
    DICTIONARY_V2 = 3;
  }
  optional Kind kind = 1;
  optional uint32 dictionarySize = 2;

  // The encoding of the bloom filters for this column:
  //   0 or missing = none or original
  //   1            = ORC-135 (utc for timestamps)
  optional uint32 bloomEncoding = 3;
}

这个有点复杂,一个footer中有若干个stream和若干个column encoding,参见https://orc.apache.org/docs/encodings.html,这里面讲了stream和column encoding到底是啥关系。这块太复杂了,后面慢慢补充。

Index

Stripe的data部分就不用定义了,反正就是二进制数据。Index部分的定义如下:

message RowIndexEntry {
  repeated uint64 positions = 1 [packed=true];
  optional ColumnStatistics statistics = 2;
}

message RowIndex {
  repeated RowIndexEntry entry = 1;
}

message BloomFilter {
  optional uint32 numHashFunctions = 1;
  repeated fixed64 bitset = 2;
  optional bytes utf8bitset = 3;
}

message BloomFilterIndex {
  repeated BloomFilter bloomFilter = 1;
}

详细的参见:
https://orc.apache.org/docs/indexes.html
https://orc.apache.org/docs/spec-index.html

内容概要:本文围绕基于CNN-Transformer混合模型的锂电池SOH(State of Health,健康状态)预测估计展开研究,提出一种融合卷积神经网络(CNN)Transformer架构的深度学习方法,用于精准建模电池容量衰退过程。该方法充分发挥CNN在局部特征提取方面的优势以及Transformer在捕捉长时间序列依赖关系上的强大能力,有效提升了锂电池健康状态预测的准确性稳定性。研究内容涵盖数据预处理、模型结构设计、训练优化流程及预测结果可视化等关键环节,适用于电池退化趋势分析剩余使用寿命(RUL)评估,具有较强的工程应用价值。; 适合人群:具备Python编程能力和深度学习理论基础的高校研究生、科研人员及从事新能源电池管理系统开发的工程技术人才,特别适合聚焦于锂电池寿命预测、故障诊断健康管理等方向的研究者。; 使用场景及目标:①掌握CNNTransformer在时间序列回归任务中的协同建模机制;②实现高精度锂电池SOH预测模型构建训练;③服务于电动汽车续航管理、储能系统运维决策电池老化特性分析;④支持学术论文复现、科研项目验证及工业级电池管理算法开发。; 阅读建议:此资源以代码实践为核心驱动,建议读者结合所提供的完整Python代码进行动手实现,深入理解模型各模块的设计逻辑训练技巧,并可通过调整网络结构或引入新数据集进一步拓展至其他时序预测任务中。
我们把同一标的(昆仑万维,现价 43.20 元,2026-07-31 收盘)交给三套系统,各出一份独立分析: **C 报告(CoordClaw 基于管理学多智能体系统)**——投研级。它由五个角色构成:周婷整合撰写、李静出基本面、王芳出技术面、赵明出风险、陈默做 PM 终审。最终产物是一份 38 项分级风险清单(P0×4 / P1×12 / P2×12 / P3×6 / 尾部×4)、双源交叉验证的财务数据(EM/Sina 差异 <0.01%)、严格的口径纪律,以及一份原样保留的"待核实"清单。结论冷冰冰:高风险,不建议参。 **D 报告(DeepSeek)**——信息整理级。它把"4+3 AGI 战略"、天工 AI、Opera 浏览器、StarMaker 拆得很漂亮,核心财务数据(营收 81.98 亿、归母 -15.93 亿)也没算错。但整篇没有技术面、没有量化风控,更关键的是——它完全没提实控人已减持 75%、质押状态未知、净现金仅 15.19 亿且续航只有 1.26~1.81 年这些要命的负面。这是典型的"选择性呈现"。 **K 报告(Kimi)**——以对比评估的方式呈现。它搭起"数据准确性 / 分析维度 / 结论合理性"的三维框架,把几份材料放在一起对照,给出各自的强弱判定。它的维度意识比 D 报告更自觉,但作为一份独立分析,它对"评估方法本身的信度"交待不足,部分引用的核对也不够彻底。 结果两家的结论高度一致。C 报告(多智能体)被评投研级、居首;D 报告(DeepSeek 自己写的)被评信息整理级、居中;K 报告(Kimi 自己那份)维度较全但核验深度有限,排在两者之间。DeepSeek 的那份评估把 C 给了五星、D 三星、K 四星;Kimi 的那份评估也独立地把最高分给了 C。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值