ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Apache Arrow C++ Array Builders 全面指南:从基础 Array 到 RecordBatch 的构建器体系详解

Apache Arrow C++ Array Builders 全面指南:从基础 Array 到 RecordBatch 的构建器体系详解 Apache Arrow C Array Builders 全面指南从基础 Array 到 RecordBatch 的构建器体系详解【免费下载链接】arrowApache Arrow is the universal columnar format and multi-language toolbox for fast data interchange and in-memory analytics项目地址: https://gitcode.com/GitHub_Trending/arrow3/arrow本指南以 Apache Arrow C 官方 API 文档中 Array Builders 章节 为骨架系统讲解 Arrow C 中用于增量构建列式数组的 Builder 体系包括基类ArrayBuilder的核心方法、Primitive / Temporal / Binary-like / Nested / Dictionary 五大类具体构建器以及面向多列批处理的RecordBatchBuilder。读完本文你将掌握各类 Builder 的选型、Append/Reserve/Finish的完整使用流程并能结合源码理解其内部基于 Buffer 与有效性位图null bitmap的工作机制从而高效地在 C 程序中构造 Arrow 数组与 RecordBatch。一、Builder 体系概览为什么需要构建器而非直接分配内存Arrow 数组在内存中是缓冲区的集合一个有效性位图null bitmap加上若干数据缓冲区。直接手工维护这些缓冲区例如追加一个元素后同步更新位图、处理变长数据的 offset 数组极易出错。Builder 正是为此设计的增量构造工具用户只需要反复调用Append系列方法追加逻辑值Builder 负责在底层缓冲区内完成内存扩容、位图维护、长度与空值计数统计。从文档与源码结构看C 构建器体系以抽象基类 ArrayBuilder 为根其下按数据类型分为五类具体子类外加用于多列批处理的RecordBatchBuilder类别构建器产出数组PrimitiveNullBuilder、BooleanBuilder、NumericBuilderTInt8/16/32/64Builder、UInt8/16/32/64Builder、FloatBuilder、DoubleBuilder、HalfFloatBuilder标量数值/布尔/null 数组TemporalDate32Builder、Date64Builder、Time32Builder、Time64Builder、TimestampBuilder、MonthIntervalBuilder、DurationBuilder时间相关数组Binary-likeBinaryBuilder、StringBuilder、LargeBinaryBuilder、LargeStringBuilder、BinaryViewBuilder、StringViewBuilder、FixedSizeBinaryBuilder变长/定长二进制与字符串数组NestedListBuilder、LargeListBuilder、ListViewBuilder、LargeListViewBuilder、MapBuilder、FixedSizeListBuilder、StructBuilder嵌套类型数组DictionaryDictionaryBuilderT、Dictionary32BuilderT及BinaryDictionaryBuilder等别名字典编码数组BatchRecordBatchBuilderRecordBatch多列这些分组对应头文件中的 Doxygen 分组定义numeric-builders、temporal-builders、binary-builders、nested-builders、dictionary-builders见 builder_base.h。二、ArrayBuilder 基类所有构建器的通用契约ArrayBuilder是抽象基类builder_base.h负责通用状态长度length_、空值计数null_count_、容量capacity_与内部有效性位图构建器null_bitmap_builder_的管理文档注释明确说明用户应当使用下面的具体构建器类型例如指向BinaryBuilder的ArrayBuilder*应向下转型后再使用。2.1 核心成员方法length()/null_count()/capacity()返回当前已追加元素数、空值数、已分配容量。Resize(int64_t capacity)确保总共能容纳capacity个元素含已追加的。文档强调它不覆盖变长数据如 binary 值所需的额外空间为增量追加预留空间应使用Reserve。Reserve(int64_t additional_capacity)确保还能再追加additional_capacity个元素而不触发重新分配。注意它是相对当前元素数而非容量的且会按BufferBuilder::GrowByFactor的增长因子超量分配以减少频繁扩容builder_base.h。AppendNull()/AppendNulls(int64_t length)追加单个/多个空值纯虚函数。AppendEmptyValue()/AppendEmptyValues(int64_t length)追加空但有效的值——对应的内存槽会被初始化但不保证具体语义用于在向父嵌套类型追加空值时保证子槽位已初始化。AppendScalar(const Scalar)/AppendScalars(const ScalarVector)从标量对象追加AppendArraySlice(...)可从同类型数组切片追加默认返回NotImplemented由子类覆盖。Finish(std::shared_ptrArray*)/Resultshared_ptrArray Finish()结束构建并产出Array对象构建器被重置DictionaryBuilder除外见第七节FinishInternal是子类实现产物为内部ArrayData的钩子。child(int i)/child_builder(int i)/num_children()嵌套类型访问子构建器返回裸指针因为子构建器由本类持有。type()返回将产出的数组类型纯虚函数。2.2 工厂函数按类型自动创建构建器不必手工挑选具体 Builder 类可以用工厂函数按数据类型动态创建builder_base.hMakeBuilder(pool, type)为给定数据类型构造对应构建器返回std::unique_ptrArrayBuilder。MakeBuilderExactIndex(pool, type)当类型中嵌套有字典类型时严格使用类型指定的索引类型默认MakeBuilder可能调整。MakeDictionaryBuilder(pool, type, dictionary)构造带预置字典的字典构建器dictionary可为空指针。此外基类还提供了ArrayBuilderExtraOps混入工具builder_base.hAppendOrNull(std::optionalV)与UnsafeAppendOrNull允许直接追加std::optional——有值则追加值无值则追加 null。2.3 容量相关常量源码中定义了两个对嵌套与列表构建器意义重大的常量builder_base.hkMinBuilderCapacity 1 532各构建器Resize时的最小容量下限。kListMaximumElements INT32_MAX - 1列表类型32 位 offset可容纳的最大元素数上限。三、Primitive 构建器Null / Boolean / 数值类型3.1 NullBuilderNullBuilder 产出NullArray其type()恒为null()。它的AppendNull/AppendNulls只递增null_count_与length_不分配任何数据缓冲区——这是最轻量的构建器。它还提供了便捷的Append(std::nullptr_t)重载以及Finish(std::shared_ptrNullArray*)的强类型版本。3.2 NumericBuilder数值构建器统一由模板 NumericBuilder 生成内部维护一个TypedBufferBuildervalue_type data_builder_与基类的有效性位图构建器。源码为所有标准数值类型提供了别名builder_primitive.husing UInt8Builder NumericBuilderUInt8Type; // 同理 UInt16/32/64 using Int8Builder NumericBuilderInt8Type; // 同理 Int16/32/64 using FloatBuilder NumericBuilderFloatType; using DoubleBuilder NumericBuilderDoubleType;HalfFloatBuilderbuilder_primitive.h是特化版本追加时接受arrow::util::Float16或原始uint16_t位模式并提供GetValueFloat16(index)模板读取。核心方法与变长类型不同数值构建器没有 offset 数组只有数据缓冲区 位图Append(value_type val)追加单个值自动Reserve(1)。AppendNull()/AppendNulls(length)追加空值数据槽位写 0防止未初始化内存访问源码注释明确说明这一点。一系列批量AppendValues重载AppendValues(const value_type* values, int64_t length, const uint8_t* valid_bytes NULLPTR)追加连续 C 数组valid_bytes中非零表示有效为nullptr时全部视为有效。带bitmapbitmap_offset的版本从既有有效性位图复制。带std::vectorbool is_valid的版本。迭代器版本AppendValues(begin, end[, valid_begin])。GetValue(index)/GetMutableValue(index)读取/就地修改第index个值operator[]也可读写。UnsafeAppend(val)/UnsafeAppendNull()/UnsafeAdvance(length)不检查容量的快速路径调用前须自行ReserveUnsafeAdvance配合GetMutableValue直接填充内存后推进游标。Finish(std::shared_ptrInt64Array*)等强类型 Finish 版本。以 compute_and_write_csv_example.cc 为例一个典型的完整使用流程arrow::NumericBuilderarrow::Int64Type int64_builder; arrow::BooleanBuilder boolean_builder; // 预分配 8 个元素容量 ARROW_RETURN_NOT_OK(int64_builder.Resize(8)); // 批量追加 std::vectorint64_t int64_values {1, 2, 3, 4, 5, 6, 7, 8}; ARROW_RETURN_NOT_OK(int64_builder.AppendValues(int64_values)); // 结束构建 std::shared_ptrarrow::Array array_a; ARROW_RETURN_NOT_OK(int64_builder.Finish(array_a)); int64_builder.Reset(); // Finish 后构建器已重置此处为显式强调3.3 BooleanBuilderBooleanBuilder 底层用位图存储布尔值每个元素 1 bit支持Append(bool)/Append(uint8_t)非零视为 true、UnsafeAppend(bool|uint8_t)。批量AppendValues支持uint8_t*数组非零即 1、std::vectoruint8_t、std::vectorbool及迭代器形式均可搭配valid_bytes/is_valid。AppendNull/AppendNulls追加空值时写入false位。四、Temporal 构建器时间类型即带语义的整数文档将时间类构建器单独归组。从源码看它们并非独立实现而是NumericBuilder的类型别名builder_primitive.husing Date32Builder NumericBuilderDate32Type; using Date64Builder NumericBuilderDate64Type; using Time32Builder NumericBuilderTime32Type; using Time64Builder NumericBuilderTime64Type; using TimestampBuilder NumericBuilderTimestampType; using MonthIntervalBuilder NumericBuilderMonthIntervalType; using DurationBuilder NumericBuilderDurationType;因此它们拥有与数值构建器完全一致的 APIAppend、AppendValues、Reserve、Finish区别仅在于产出的数组类型带有时间语义如TimestampType携带 timezone 与单位信息。MonthDayNanoIntervalType、DayTimeIntervalType等复合间隔类型则走嵌套/专用实现不在此别名之列。五、Binary-like 构建器变长字符串与二进制变长类型需要额外的offset 缓冲区来记录每个元素的起止位置内部结构为「null bitmap offsets value data」三缓冲区。基类BaseBinaryBuilder用offsets_builder_与value_data_builder_分别维护 offset 数组与数据字节流派生类构成如下构建器产出数组说明BinaryBuilderBinaryArray变长二进制32 位 offsetStringBuilderStringArrayUTF-8 字符串type()为utf8()校验语义由上层保证LargeBinaryBuilder/LargeStringBuilderLargeBinaryArray/LargeStringArray64 位 offset支持超大数组BinaryViewBuilder/StringViewBuilderBinaryViewArray/StringViewArrayBinary View 布局FixedSizeBinaryBuilderFixedSizeBinaryArray定长字节无 offset 数组5.1 常用 API以 StringBuilder 为例Append(std::string_view)/Append(const char*, length)/Append(const uint8_t*, length)追加一个字符串。AppendNull()/AppendNulls(length)追加空值此时仅推进 offset不写数据。AppendValues(const std::vectorstd::string, valid_bytes NULLPTR)与AppendValues(const char** values, length, valid_bytes)批量追加对char*版本若某指针为NULL即使valid_bytes标记有效也会按空值处理源码有专门逻辑。ReserveData(int64_t elements)为 value data 缓冲区预留字节数避免追加大数据时反复分配单数组上限为INT32_MAX - 1字节memory_limit()超限返回Status::CapacityError。ExtendCurrent(...)扩展最后一个已追加值的尾部数据不新增 offset。GetView(i)/GetValue(i, out_length)临时读取第i个值指针在下一次修改操作后失效。Finish(std::shared_ptrStringArray*)。注意FinishInternal实现builder_binary.h会补写最后一个 offset即数据总长度这是变长数组布局的硬性要求。5.2 Binary View 与定长二进制BinaryViewBuilder/StringViewBuilderbuilder_binary.h短字符串≤BinaryViewType::kInlineSize内联进 view 结构体长字符串落入由StringHeapBuilder管理的 32KB 数据块堆默认kDefaultBlocksize 32 10。可用SetBlockSize()调整块大小增大减少分配频率、减小降低单次分配开销。FixedSizeBinaryBuilderbuilder_binary.h构造时需传入含byte_width的FixedSizeBinaryType每个值固定占用byte_width字节Append(const uint8_t*)等重载接收定长数据UnsafeAdvance按byte_width倍数推进。5.3 分块变长构建器为避免单数组超过 2GB 上限internal/ChunkedBinaryBuilder及ChunkedStringBuilder自动将数据切分为多个 chunk构造参数max_chunk_value_length限制单 chunk 数据字节数、max_chunk_length限制元素数Finish产出一组Array的向量。六、Nested 构建器List、Map、FixedSizeList 与 Struct嵌套构建器通过持有子构建器children_实现递归构造父层只维护自己的 offset 与有效性位图。它们共用一个核心思想先往子构建器追加元素再用Append界定期限delimiter。6.1 ListBuilder / LargeListBuilderListBuilder 与LargeListBuilder64 位 offset的使用模式为auto values_builder std::make_sharedarrow::Int64Builder(); arrow::ListBuilder list_builder(arrow::default_memory_pool(), values_builder); // 开始第一个列表槽位 ARROW_RETURN_NOT_OK(list_builder.Append()); // 界定期限 ARROW_RETURN_NOT_OK(values_builder-Append(1)); ARROW_RETURN_NOT_OK(values_builder-Append(2)); // 该槽位包含 [1, 2] ARROW_RETURN_NOT_OK(list_builder.Append()); // 开始下一个槽位 ARROW_RETURN_NOT_OK(values_builder-AppendNull()); // 槽位含一个 null std::shared_ptrarrow::Array out; ARROW_RETURN_NOT_OK(list_builder.Finish(out));基础模板VarLengthListLikeBuilder提供Append(bool is_valid, int64_t list_length)而BaseListBuilder::Append(bool)在 List 中更简单——槽位长度由下一次Append/Finish前追加的子元素数自动决定。还有批量 APIAppendValues(const offset_type* offsets, int64_t length, valid_bytes)直接按 offset 序列追加。元素总数上限maximum_elements()为INT32_MAX - 1builder_nested.h。6.2 ListViewBuilder / LargeListViewBuilderListViewBuilder/LargeListViewBuilderbuilder_nested.h是视图型列表每个槽位显式记录(offset, size)两个维度允许多个槽位共享/重叠底层元素。因此其Append(is_valid, list_length)中的list_length必须精确给出槽位长度批量 APIAppendValues(offsets, sizes, length, valid_bytes)需要同时提供 offset 与 size 两个数组见BaseListViewBuilder实现 builder_nested.h。6.3 MapBuilderMapBuilder 在物理上是一个条目为 struct(key, item) 的 list但公开 API 拆分为键/值两个构建器auto key_builder std::make_sharedarrow::StringBuilder(); auto item_builder std::make_sharedarrow::Int64Builder(); arrow::MapBuilder map_builder(arrow::default_memory_pool(), key_builder, item_builder); ARROW_RETURN_NOT_OK(map_builder.Append()); // 开始一个新 map ARROW_RETURN_NOT_OK(key_builder-Append(k1)); ARROW_RETURN_NOT_OK(item_builder-Append(42)); ARROW_RETURN_NOT_OK(map_builder.AppendNull()); // 空 map源码注释明确key 的唯一性与有序性不做校验构造参数keys_sorted仅记录到类型元数据中。也可用value_builder()直接以list of struct方式追加条目。6.4 FixedSizeListBuilderFixedSizeListBuilder 每个槽位长度固定为构造时的list_sizeAppend()只更新有效性位图子值必须由value_builder()追加AppendNull()会自动在子构建器中补齐list_size个空值。6.5 StructBuilderStructBuilder 构造时传入字段构建器向量Append(bool is_valid true)只维护本层位图每个子构建器必须独立调用其 Append 以保持结构一致AppendNull()则会自动向每个子构建器追加空值。批量 APIAppendValues(length, valid_bytes)只处理位图子数据仍需各自追加。访问子构建器用field_builder(int i)/num_fields()。七、Dictionary 构建器字典编码与增量字典字典编码将重复值映射为整数索引显著压缩基数低的列。DictionaryBuilderTbuilder_dict.h基于AdaptiveIntBuilder作为索引构建器——索引位数自动增长始终使用能容纳当前字典大小的最小整数宽度Dictionary32BuilderT则固定使用Int32Builder便于跨 chunk 保持一致的索引类型如构造ChunkedArray。文档还列出了常用别名using BinaryDictionaryBuilder DictionaryBuilderBinaryType; using StringDictionaryBuilder DictionaryBuilderStringType; using BinaryDictionary32Builder Dictionary32BuilderBinaryType; using StringDictionary32Builder Dictionary32BuilderStringType;7.1 与普通构建器不同的语义内部维护DictionaryMemoTable哈希表builder_dict.hAppend(value)先GetOrInsert拿到 memo 索引再将该索引追加进索引构建器——相同值只存一份字典项。dictionary_length()当前字典条目数。AppendNull()追加空索引。InsertMemoValues(const Array)预填充字典配合工厂函数MakeDictionaryBuilder预置字典。AppendArray(const Array)把整个稠密数组解包后逐元素追加。Finish()不重置字典 memo这是文档明确指出的特殊行为——再次Finish会产出基于同一字典的新数组。若需连字典一起清空调用ResetFull()。FinishDelta(out_indices, out_delta)产出自上次 Finish 以来的索引增量 字典增量适合流式/分块编码场景。八、RecordBatchBuilder按 Schema 构建多列批次当需要按已知 Schema 迭代地构造多条RecordBatch时RecordBatchBuildertable_builder.h把每个字段的ArrayBuilder集中管理起来。8.1 创建与访问auto schema arrow::schema({arrow::field(id, arrow::int64()), arrow::field(name, arrow::utf8())}); ARROW_ASSIGN_OR_RAISE(auto builder, arrow::RecordBatchBuilder::Make(schema, arrow::default_memory_pool())); // 或指定初始容量 ARROW_ASSIGN_OR_RAISE(auto builder2, arrow::RecordBatchBuilder::Make(schema, pool, /*initial_capacity*/1024));GetField(int i)返回第i个字段的基础ArrayBuilder*。GetFieldAsT(int i)checked_cast到具体类型例如builder-GetFieldAsarrow::Int64Builder(0)。SetInitialCapacity(int64_t)/initial_capacity()设置/读取新构建器的初始容量。schema()/num_fields()访问 Schema 与字段数。8.2 产出批次auto* id_builder builder-GetFieldAsarrow::Int64Builder(0); auto* name_builder builder-GetFieldAsarrow::StringBuilder(1); ARROW_RETURN_NOT_OK(id_builder-Append(1)); ARROW_RETURN_NOT_OK(name_builder-Append(alice)); ... // 结束当前批次并重置构建器 ARROW_ASSIGN_OR_RAISE(std::shared_ptrarrow::RecordBatch batch, builder-Flush()); // Flush(true) 与 Flush() 等价均重置Flush(false) 不重置可继续追加第二批Flush(bool reset_builders)完成所有字段数组并组装RecordBatchFlush()等价于Flush(true)。九、实战速查完整构建一个含空值的 Int64 数组综合本节内容一个覆盖「预分配 → 单值/批量/空值追加 → 强类型 Finish → 校验」的完整示例模式参照 compute_and_write_csv_example.cc#include arrow/api.h arrow::Status Run() { arrow::Int64Builder builder; // 1. 预分配 ARROW_RETURN_NOT_OK(builder.Reserve(6)); // 2. 追加 ARROW_RETURN_NOT_OK(builder.Append(10)); ARROW_RETURN_NOT_OK(builder.AppendNull()); // 空值数据槽写 0 std::vectorint64_t values {30, 40}; ARROW_RETURN_NOT_OK(builder.AppendValues(values)); // 批量追加 std::vectoruint8_t valid {1, 0}; // 第二个为 null ARROW_RETURN_NOT_OK(builder.AppendValues({50, 60}, valid)); // 3. 结束构建 ARROW_ASSIGN_OR_RAISE(std::shared_ptrarrow::Array array, builder.Finish()); auto int64_array std::static_pointer_castarrow::Int64Array(array); std::cout length int64_array-length() null_count int64_array-null_count() std::endl; return arrow::Status::OK(); }若事先不知道类型可改用工厂ARROW_ASSIGN_OR_RAISE(auto b, arrow::MakeBuilder(type));之后以ArrayBuilder*统一驱动Reserve/AppendScalar/Finish。十、总结构建器选型速查你的需求选用构建器关键注意点数值/布尔/null 列NumericBuilderT、BooleanBuilder、NullBuilder空值槽写 0批量用AppendValues热路径用UnsafeAppend时间列TimestampBuilder等即 NumericBuilder 别名单位为类型元数据的一部分字符串/二进制StringBuilder/BinaryBuilder或 Large/View 变体记得ReserveDataFinish自动补写末 offset列表/映射/结构体ListBuilder、MapBuilder、StructBuilder先子构建器追加再父层Append界定期限高基数去重列StringDictionaryBuilder等Finish不清空字典分块用FinishDelta多列批次RecordBatchBuilderGetFieldAsT获取字段构建器Flush()产出批次构建器体系是 Arrow C 中最常用的编程入口之一无论是组装测试数据、实现导入管线还是构造 IPC 消息体掌握Append 语义 Reserve 容量管理 Finish 产物这一固定节奏再配合本仓库中 array 目录 下的构建器实现与 examples 中的可运行示例即可得心应手地构建任意复杂度的 Arrow 列式数据。【免费下载链接】arrowApache Arrow is the universal columnar format and multi-language toolbox for fast data interchange and in-memory analytics项目地址: https://gitcode.com/GitHub_Trending/arrow3/arrow创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表