
Apache Arrow C 数组机制详解Array、ArrayBuilder 构建、ChunkedArray 与零拷贝 Slice【免费下载链接】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 官方文档 docs/source/cpp/arrays.rst 为核心系统讲解 Arrow 中数组arrow::Array的内存布局、通过ArrayBuilder系构建数组的完整流程与性能优化手段Reserve/Resize/UnsafeAppend、ChunkedArray分块逻辑序列、零拷贝Slice以及面向示例与测试的*FromJSONString辅助函数。读完后你将能够独立编写 C 代码高效地构建、访问和分片 Arrow 数组并理解每一行示例背后的缓冲区Buffer语义与源码级实现。1. 核心类型arrow::Array 及其内存布局Arrow 体系中的中心类型是 arrow::Array。一个数组表示已知长度、同一数据类型的值的序列。其内部由一个或多个arrow::Buffer构成缓冲区的数量与含义完全取决于数组的数据类型遵循 Arrow 数据布局规范Format Layout Specification。这些缓冲区包含两部分内容值数据本身按类型布局存放如定长数值类型的裸数据缓冲区可选的 null bitmap有效位图标记哪些条目为 null。如果确定数组中没有 null 值该位图缓冲区可以整个省略——这是 Arrow 布局的一个重要特性。对于每种数据类型arrow::Array都有具体的子类帮助访问单个值。例如Int64Array本质上只是arrow::NumericArrayInt64Type的便捷 typedef。构建数组的两种总体策略Arrow 对象是不可变的immutable不能像std::vector那样直接填充。因此共有两类策略数据已在内存中就绪若数据已按 Arrow 布局存在于内存中可将内存包装进arrow::Buffer实例再构造描述该数组的arrow::ArrayData涉及内存管理时可参考官方 memory 文档 docs/source/cpp/memory.rst增量构建使用基类 arrow::ArrayBuilder 及其具体子类逐值或批量地构建数组数据无需自己处理 Arrow 格式细节。此外对于示例或测试等对性能不敏感的场景可以直接使用*FromJSONString辅助函数用 JSON 文本简写快速创建数组见本文第 5 节。2. 使用 ArrayBuilder 及其子类以构建一个Int64类型的 Arrow 数组为例可以使用arrow::Int64Builder。下面的例子构建 1 到 8 的序列其中应存放 4 的第四个位置为 nullarrow::Int64Builder builder; builder.Append(1); builder.Append(2); builder.Append(3); builder.AppendNull(); builder.Append(5); builder.Append(6); builder.Append(7); builder.Append(8); auto maybe_array builder.Finish(); if (!maybe_array.ok()) { // ... do something on array building failure } std::shared_ptrarrow::Array array *maybe_array;构建完成后得到的 Array可向下转型为具体的arrow::Int64Array以便访问值由两个arrow::Buffer组成第一个 buffer 保存 null bitmap此处仅 1 个字节位模式为1|1|1|1|0|1|1|1。由于采用最低有效位LSB编号这表示数组的第四个条目为 null第二个 buffer 就是存放上述全部值的int64_t数组。因为第四个条目是 null该位置在 buffer 中的值是无定义的undefined。访问具体数组内容// Cast the Array to its actual type to access its data auto int64_array std::static_pointer_castarrow::Int64Array(array); // Get the pointer to the null bitmap const uint8_t* null_bitmap int64_array-null_bitmap_data(); // Get the pointer to the actual data const int64_t* data int64_array-raw_values(); // Alternatively, given an array index, query its null bit and value directly int64_t index 2; if (!int64_array-IsNull(index)) { int64_t value int64_array-Value(index); }源码层面的对应关系arrow::Int64Array分别是arrow::NumericArrayInt64Type及arrow::NumericBuilderInt64Type的 typedef提供便捷访问接口。Builder 侧的类型别名定义可见 builder_primitive.husing Int64Builder NumericBuilderInt64Type;Finish 的语义返回 Result 并重置 Builder从基类 builder_base.h 可以看到Finish有两种重载/// The builder is reset except for DictionaryBuilder. Status Finish(std::shared_ptrArray* out); Resultstd::shared_ptrArray Finish();注意注释除DictionaryBuilder外Finish 会重置 builder——这正是下节ChunkedArray示例中可以复用同一个 builder 构建第二个 chunk 的原因。FinishInternal则返回内部通用的ArrayData对象供嵌套类型 builder 组合使用。3. 性能优化Reserve、Resize、AppendValues 与 UnsafeAppend虽然可以像上文示例那样逐值构建但要获得最高性能推荐使用具体arrow::ArrayBuilder子类中的批量追加方法通常名为AppendValues。如果预先知道元素数量还推荐通过arrow::ArrayBuilder::Resize或arrow::ArrayBuilder::Reserve方法预置工作区。改写后的批量版本示例arrow::Int64Builder builder; // Make place for 8 values in total builder.Reserve(8); // Bulk append the given values (with a null in 4th place as indicated by the // validity vector) std::vectorbool validity {true, true, true, false, true, true, true, true}; std::vectorint64_t values {1, 2, 3, 0, 5, 6, 7, 8}; builder.AppendValues(values, validity); auto maybe_array builder.Finish();若必须逐个追加部分具体 builder 子类提供标记为 Unsafe 的方法它们假设工作区已被正确预置容量以省去容量检查为代价换取更高性能arrow::Int64Builder builder; // Make place for 8 values in total builder.Reserve(8); builder.UnsafeAppend(1); builder.UnsafeAppend(2); builder.UnsafeAppend(3); builder.UnsafeAppendNull(); builder.UnsafeAppend(5); builder.UnsafeAppend(6); builder.UnsafeAppend(7); builder.UnsafeAppend(8); auto maybe_array builder.Finish();源码中的 Resize 与 Reserve 语义差异阅读 ArrayBuilder 基类 的注释可以精确区分两者的语义Resize(int64_t capacity)确保已分配足够内存容纳指定数量的总元素含已追加的capacity必须大于当前容量不能缩小源码中会返回Invalid状态。对变长数据如 binary不保证覆盖因重新分配产生的额外开销Reserve(int64_t additional_capacity)增量追加空间。注意additional_capacity是相对于当前元素数量而非当前容量计算的内部会调用BufferBuilder::GrowByFactor做超分配overallocation以最小化多次增量Reserve()的影响UnsafeAppend*系列属于基类 protected 的 Unsafe operations (dont check capacity/dont resize) 类别例如UnsafeAppendToBitmap(bool is_valid)直接向 null bitmap builder 写入位不做任何容量检查。这一实现印证了文档的性能建议预置容量 批量/Unsafe 追加可以完全避免逐次调用时的边界检查与扩容判断。4. 尺寸限制与 ChunkedArray尺寸限制部分数组类型在结构上受限于 32 位尺寸例如list 数组最多容纳 2^31 个元素string 数组与 binary 数组至少受限于 2GB 的二进制数据量。其他数组类型在 C 实现中可容纳多达 2^63 个元素但其他 Arrow 语言实现对这些类型同样可能存在 32 位尺寸限制。基于此官方建议对超大数据应按更合理的规模切分为 chunk。ChunkedArray逻辑连续、物理分块arrow::ChunkedArray定义见 chunked_array.h与数组一样是一个逻辑值序列但与简单数组不同的是它不要求整个序列在内存中物理连续。组成 chunked array 的各分块可以大小不一但必须具有相同的数据类型。Chunked array 通过聚合任意数量的 array 构建。以下示例用两个独立 chunk 构建与上文相同的逻辑值序列std::vectorstd::shared_ptrarrow::Array chunks; std::shared_ptrarrow::Array array; // Build first chunk arrow::Int64Builder builder; builder.Append(1); builder.Append(2); builder.Append(3); if (!builder.Finish(array).ok()) { // ... do something on array building failure } chunks.push_back(std::move(array)); // Build second chunk builder.Reset(); builder.AppendNull(); builder.Append(5); builder.Append(6); builder.Append(7); builder.Append(8); if (!builder.Finish(array).ok()) { // ... do something on array building failure } chunks.push_back(std::move(array)); auto chunked_array std::make_sharedarrow::ChunkedArray(std::move(chunks)); assert(chunked_array-num_chunks() 2); // Logical length in number of values assert(chunked_array-length() 8); assert(chunked_array-null_count() 1);注意第二个 chunk 的构建直接复用了同一个builder——调用builder.Reset()后重新追加。这与源码中Finish会重置 builder 的语义builder_base.h 的Reset()与Finish注释完全一致。最终断言的三个值体现了关键区别length()返回的是逻辑长度8 个值而num_chunks()是物理分块数2null_count()跨所有 chunk 汇总为 1。5. Slicing数组的零拷贝切片与物理内存 buffer 类似数组和 chunked 数组同样支持零拷贝切片得到仅引用数据某个逻辑子序列的数组或 chunked 数组而不复制底层 buffer。实现方式是分别调用arrow::Array::Slice与arrow::ChunkedArray::Slice方法。这一机制在读取器如 IPC/CSV reader中极为常见——底层共享同一份 buffer通过 offset length 表达不同的逻辑视图从而让分页读取、内存映射等场景零成本。6. FromJSONString 辅助函数仓库为示例、测试或快速原型提供了一组辅助函数可以从 JSON 文本简洁地创建Array和Scalar。这些辅助函数明确不应用于性能敏感场景——大多数用户应使用 JSON 文档 中描述的 API它提供了从行分隔 JSON 文件高性能创建arrow::Table和arrow::RecordBatch的方式。仓库中的可运行示例 from_json_string_example.cc 展示了ArrayFromJSONString、ChunkedArrayFromJSONString、DictArrayFromJSONString三类辅助函数的用法以下即该示例RunExample()的完整代码// Simple types ARROW_ASSIGN_OR_RAISE(auto int32_array, ArrayFromJSONString(arrow::int32(), [1, 2, 3])); ARROW_ASSIGN_OR_RAISE(auto float64_array, ArrayFromJSONString(arrow::float64(), [4.0, 5.0, 6.0])); ARROW_ASSIGN_OR_RAISE(auto bool_array, ArrayFromJSONString(arrow::boolean(), [true, false, true])); ARROW_ASSIGN_OR_RAISE( auto string_array, ArrayFromJSONString(arrow::utf8(), R([Hello, World, null]))); // Timestamps can be created from string representations ARROW_ASSIGN_OR_RAISE( auto ts_array, ArrayFromJSONString(timestamp(arrow::TimeUnit::SECOND), R([1970-01-01, 2000-02-29,3989-07-14,1900-02-28]))); // List, Map, Struct ARROW_ASSIGN_OR_RAISE( auto list_array, ArrayFromJSONString(list(arrow::int64()), [[null], [], null, [4, 5, 6, 7, 8], [2, 3]])); ARROW_ASSIGN_OR_RAISE( auto map_array, ArrayFromJSONString(map(arrow::utf8(), arrow::int32()), R([[[joe, 0], [mark, null]], null, [[cap, 8]], []]))); ARROW_ASSIGN_OR_RAISE( auto struct_array, ArrayFromJSONString( arrow::struct_({field(one, arrow::int32()), field(two, arrow::int32())}), [[11, 22], null, [null, 33]])); // ChunkedArrayFromJSONString ARROW_ASSIGN_OR_RAISE( auto chunked_array, ChunkedArrayFromJSONString(arrow::int32(), {[5, 10], [null], [16]})); // DictArrayFromJSONString ARROW_ASSIGN_OR_RAISE( auto dict_array, DictArrayFromJSONString(dictionary(arrow::int32(), arrow::utf8()), [0, 1, 0, 2, 0, 3], R([k1, k2, k3, k4])));从示例可以归纳这些辅助函数支持的类型覆盖面简单类型整型、浮点、布尔、字符串JSON 中null直接映射为 null 值时间戳可从 ISO 风格的字符串表示创建示例用TimeUnit::SECOND嵌套类型list、map、structJSON 的嵌套结构直接对应 Arrow 的嵌套布局ChunkedArrayChunkedArrayFromJSONString接收一个字符串序列每个 JSON 字符串成为一个独立 chunk字典数组DictArrayFromJSONString的第一个 JSON 字符串是 indices如[0, 1, 0, 2, 0, 3]第二个是 dictionary values如[k1, k2, k3, k4]组合出dictionary(int32, utf8)数组。这些函数的声明位于 cpp/src/arrow/json/from_string.h实现见 from_string.cc对应测试为 from_string_test.cc。完整的辅助函数清单可参考 C API 参考中的 Array APIdocs/source/cpp/api/array.rst。7. 实践要点小结构建选路内存布局已就绪时用BufferArrayData直接包装增量构建用ArrayBuilder子类性能不敏感的示例/测试直接用*FromJSONString性能路径已知规模先Reserve增量或Resize总量批量用AppendValues逐值场景用UnsafeAppend系列并自行保证容量容量语义Reserve(n)是再容纳 n 个Resize(n)是总容量到 n两者不可混用逻辑与物理分离ChunkedArray的length()是跨 chunk 的逻辑长度超大数据应分块以规避 list/string/binary 等类型在部分实现中的 32 位尺寸限制零拷贝原则Slice不复制 buffer适合做只读子序列视图不可变约定Array一经构建不可变任何修改都意味着新对象Finish后 builder 会被重置DictionaryBuilder除外可复用。【免费下载链接】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),仅供参考