ARTICLE DETAIL

资讯详情

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

Apache Arrow C++ 数组体系全解析:从 ArrayData、Array 到 ChunkedArray 与 ArrayVisitor

Apache Arrow C++ 数组体系全解析:从 ArrayData、Array 到 ChunkedArray 与 ArrayVisitor Apache Arrow C 数组体系全解析从 ArrayData、Array 到 ChunkedArray 与 ArrayVisitor【免费下载链接】arrowApache Arrow is a multi-language toolbox for accelerated data interchange and in-memory processing项目地址: https://gitcode.com/gh_mirrors/arrow13/arrowApache Arrow 在cpp/src/arrow/array/下构建了一套完整、统一的 C 数组Array体系docs/source/cpp/api/array.rst正是这一体系的官方 API 索引它从最底层的ArrayData开始依次覆盖抽象基类Array、工厂函数、Primitive / Temporal / Binary-like / Nested / Dictionary / Extension 等具体数组子类以及逻辑上把多个 Array 聚合成一个大数组的ChunkedArray最后以类型分发的ArrayVisitor收尾。本文以该文档为主线结合仓库源码逐层剖析这套体系的类层次、内存布局、访问器语义与常用工具读完你将对 Arrow C 中一个数组到底长什么样、如何构造、如何读写、如何切分与验证有完整认识并能直接对照源码继续深挖任意一个具体数组类型。ArrayData数组内存的通用容器arrow::ArrayData是整棵数组类树的地基array.rst将它与Array并列放在文档最前面。它在 cpp/src/arrow/array/data.h 中定义注释里明确它的定位一个自包含的、描述 Arrow 数组内存与元数据的容器相当于 Java 实现中的 Vector而arrow::Array及其子类则提供强类型访问器并支持访问者模式visitor pattern。核心成员与构造ArrayData的核心字段包括字段类型含义typestd::shared_ptrDataType数组的逻辑类型如int64、utf8、listlengthint64_t数组元素个数null_countint64_t原子物理空值个数未知时用kUnknownNullCount -1表示首次访问时惰性计算offsetint64_t相对父数组数据的偏移用于零拷贝切片默认为 0buffersstd::vectorstd::shared_ptrBuffer数据缓冲区列表buffers[0]通常是有效性位图validity bitmapchild_datastd::vectorstd::shared_ptrArrayData嵌套类型的子数组数据如 List 的值数组、Struct 的字段数组dictionarystd::shared_ptrArrayData字典数组专用的字典数据null_count -1这一设计来自 ARROW-33见 data.h 注释切片时并不知道切片范围的空值个数为避免急切计算先置为负数当Array::null_count()第一次被调用时才计算并缓存。ArrayData还提供多个Make静态工厂data.h#L126-L146支持仅传类型与长度、带 buffers、带 child_data、带 dictionary 四种形态。缓冲区访问ArrayData提供了一组模板化访问器GetValuesT(i, absolute_offset)把第 i 个 buffer 按类型 T 解释为指针GetValuesSafeT在缓冲区非 CPU buffer 时返回NULLPTR而不是崩溃GetMutableValuesT则拿到可写指针。Slice(offset, length)构造零拷贝切片不复制底层 buffer只调整 offset/lengthSliceSafe是带边界检查的版本。空值判定物理与逻辑理解 Arrow 的空值模型要从ArrayData的几个方法入手HasValidityBitmap()buffers[0] ! NULLPTR即是否存在有效性位图。MayHaveNulls()有效性位图存在且null_count ! 0。MayHaveLogicalNulls()与ComputeLogicalNullCount()除了位图还会检查 union、run-end encoded、dictionary 这类没有位图但子数据可能含空的类型见 data.h 中对SPARSE_UNION/DENSE_UNION/RUN_END_ENCODED/DICTIONARY的分派。data.h#L321-L343还给出了一个迁移示例老代码用array.MayHaveNulls()判断是否需要检查位图新代码应改用MayHaveLogicalNulls()HasValidityBitmap()的组合才能正确处理 union 等特殊类型。一次典型的数据复用ArrayData注释data.h#L71-L77演示了最经典的用法——改类型不改内存Int64Array arr GetMyData(); auto new_data arr.data()-Copy(); new_data-type arrow::float64(); DoubleArray double_arr(new_data);由于 Int64 与 Float64 物理布局相同同为 8 字节定宽直接把ArrayData拷贝一份、替换type字段就能零拷贝得到一个新类型的数组。这也解释了为什么ArrayData被设计为可变的mutable而对外暴露的Array是不可变的immutable。Array不可变数组的抽象基类arrow::Arrayarray_base.h是所有用户可见数组类型的基类。它的设计哲学是不可变、零拷贝、内存由 Buffer 持有。基类只要求若空值个数大于 0则需要有效性位图 buffer若空值个数未知构造时可传 -1 让它惰性计算。常用访问器方法说明length()元素个数来自data_-lengthoffset()相对数据起点偏移零拷贝切片核心null_count()物理空值个数首次调用时计算并缓存ComputeLogicalNullCount()对无位图类型union、run-end encoded也给出正确的逻辑空值数IsNull(i)/IsValid(i)按索引判空/判有效不做边界检查返回booltype()/type_id()逻辑类型对象及其枚举 idnull_bitmap()/null_bitmap_data()有效性位图 buffer 及其裸指针GetScalar(i)把第 i 个元素包装为Scalar对象IsValid的实现值得注意array_base.h#L62-L80如果有效性位图存在直接用位运算bit_util::GetBit(null_bitmap_data_, i data_-offset)取值否则按类型分派——SPARSE_UNION、DENSE_UNION、RUN_END_ENCODED 调用internal命名空间下的专用函数其它类型则退化为null_count ! length。注释特别强调把IsNull做成内联、非虚函数是为了避免每次调用都走 vtable 查表这正是 Arrow 注重列式处理性能的体现。切片、比较与校验切片Slice(offset, length)是零拷贝的——它返回共享同一批 buffer 的新Array对象只更新offset与lengthSliceSafe增加输入检查。需要复制到其它设备时可用CopyTo(MemoryManager)递归拷贝全部 buffer 与子数组或ViewOrCopyTo优先尝试零拷贝视图失败再回退复制。比较Equals严格相等、ApproxEquals近似相等epsilon仅对 Float/Double 生效、RangeEquals指定区间比较Diff(other)返回格式化的统一 diff 文本底层是 cpp/src/arrow/array/diff.cc 的arrow::Diff。校验Validate()做轻量检查时间复杂度 O(k)k 为子孙节点数ValidateFull()做深度检查最坏 O(k·n)n 为数组长度适合调试阶段使用。视图View(type)在类型布局兼容的前提下零拷贝改变解释类型ToString()输出适合调试的 PrettyPrint 表示。设备device_type()返回底层数据所在设备CPU/GPU它委托给ArrayData::device_type()。层次划分FlatArray 与 PrimitiveArrayarray_base.h在Array之下还有两个中间基类FlatArray非嵌套数组的基类标记类无新增逻辑。PrimitiveArray定宽逻辑类型数组的基类持有raw_values_指针指向data_-buffers[1]索引 1 即数据缓冲区索引 0 是有效性位图并暴露values()方法。NullArray退化类型的特例NullArrayarray_base.h是 null 类型数组长度任意但每个元素都是 null。它的SetData直接置null_bitmap_data_ NULLPTR并把null_count强制设为length——不需要任何实际 buffer。工厂函数从 ArrayData 到 Arrayarray.rst的 Factory functions 一节引用了 Doxygen 组array-factories对应头文件 cpp/src/arrow/array/util.h 中的一组顶层函数函数作用MakeArray(data)根据ArrayData的type自动构造对应具体类型的Array最常用的入口MakeArrayOfNull(type, length, pool)构造指定类型、指定长度的全 null 数组MakeArrayFromScalar(scalar, length, pool)用标量值填充指定长度的数组其中MakeArrayOfNull与MakeArrayFromScalar返回Resultstd::shared_ptrArrayutil.h#L49-L58需要处理错误状态。实际生产代码中从 IPC 读取或从 Builder 构建出ArrayData之后通常就通过MakeArray拿到类型安全的Array对象。这些工厂函数底层依赖各具体类型构造函数按类型分派可在 cpp/src/arrow/array/array_base.cc 与 util.cc 中查看实现。具体数组子类array.rst把具体子类分成五组下面分别对应到源码文件。Primitive 与 Temporal原始与时间类型这一组包括NullArray、BooleanArray和numeric-arrays组。BooleanArrayarray_primitive.h布尔值按位紧凑存储1 元素占 1 bitValue(i)用bit_util::GetBit读取并提供false_count()/true_count()不缓存、每次重算以及begin()/end()迭代器。operator[](i)返回std::optionalboolnull 槽位返回std::nullopt。NumericArrayTYPEarray_primitive.h#L86-L120模板类按对应DataType子类实例化例如NumericArrayInt8Type、NumericArrayDate32Typevalue_type取TypeClass::c_type如int64_t、double。raw_values()返回已叠加 slice offset 的裸指针Value(i)直接下标读取operator[](i)返回std::optionalvalue_type。文档引用的numeric-arrays组覆盖整型Int8/16/32/64、UInt8/16/32/64、浮点HalfFloat、Float、Double、Decimal128/256以及时间类型Date32/64、Time32/64、Timestamp、Duration、Month/DayTime/MonthDayNano Interval。它们都有便捷别名如Int32Array即NumericArrayInt32Type。Binary-like二进制类binary-arrays组array_binary.h包括BinaryArray/StringArray定偏移int32的变长二进制/字符串value_offset(i)与value_length(i)决定第 i 个值的位置与长度LargeBinaryArray/LargeStringArrayint64 偏移版本支持超过 2 GiB 的单数组FixedSizeBinaryArray定宽二进制BinaryViewArray/StringViewArrayview 类型把短值内联、长值用 view 结构引用减少拷贝Decimal128Array/Decimal256Array在 array_decimal.h 中定义。注意 2 GiB 上限与普通 BinaryArray 偏移用 int32 有关——这正是 Large 系列存在的意义。Nested嵌套类型nested-arrays组array_nested.h包含ListArray/LargeListArray基于value_offsets缓冲区和子values数组FromArrays(offsets, values, ...)工厂负责最基础的偏移校验array_nested.h#L172-L195若 offsets 含 null 会自动分配新 offsets 数组。ListViewArray/LargeListViewArraylist 的 view 变体偏移表示的是跨度而非前缀和。FixedSizeListArray每个槽位固定包含 n 个值无需偏移数组。MapArray键值对列表的语义封装。StructArray多个子数组按位置对齐num_fields()即字段数。SparseUnionArray/DenseUnionArray联合类型不同槽位可持有不同类型。RunEndEncodedArrayarray_run_end.h游程编码数组。所有 List 变体共享模板基类VarLengthListLikeArrayarray_nested.h#L78-L137统一提供values()、value_offsets()、value_offset(i)、value_length(i)、value_slice(i)取第 i 个列表元素为一个子数组等接口并支持FlattenRecursively()把任意深度的列表展平成非列表数组。StructArray 的Flatten()则返回每个字段独立的数组。Dictionary-encoded字典编码DictionaryArrayarray_dict.h由一个非负整数索引数组加一个字典数组组成。例如[foo, bar, foo, bar, foo, bar]配字典[bar, foo]索引就是[1, 0, 1, 0, 1, 0]文档注释原例。核心 APIindices()/dictionary()分别取索引数组与字典GetValueIndex(i)把第 i 个索引转为int64_t非性能敏感场景使用FromArrays(type, indices, dictionary)构造并校验所有索引非负且小于字典大小Transpose(type, dictionary, transpose_map, pool)配合DictionaryUnifier做字典统一后的索引转置Compact(pool)压缩字典。DictionaryUnifierarray_dict.h#L126-L180是配套工具类Unify逐字典追加并产出转置索引UnifyChunkedArray/UnifyTable可跨 chunk、跨表列统一字典最终GetResult返回最小可容纳的索引类型。它只直接支持原始值类型的字典但嵌套在复杂类型内的字典会被正确统一。Extension arrays扩展数组ExtensionArrayextension_type.h是用户自定义类型的载体它包裹一个存储数组storage arraystorage()方法返回底层数组用户类型通过继承ExtensionType定义。扩展数组在逻辑上表现为用户类型但物理存储复用现有 Arrow 布局是 Arrow 类型系统可扩展性的关键。ChunkedArray逻辑上的大数组单个Array要求内存连续而现实中的数据尤其是变长二进制、字符串往往无法或不宜一次性分配为单个大数组。ChunkedArraycpp/src/arrow/chunked_array.h正是为此而生它把一组同类型的Arraychunk聚合成一个逻辑上的大数组不需要昂贵的拼接concatenation步骤。其头文件注释还给出了两条重要约定处理函数的结果 chunk 布局不保证与输入一致——API 不把保持 chunk 布局作为契约当函数输出可能超过单个Array容量如超大BinaryArray/StringArray时官方推荐直接返回ChunkedArray而不是std::vectorArray。构造与访问ChunkedArray(chunk)单 chunk 构造ChunkedArray(chunks, type)从ArrayVector构造所有 chunk 必须同类型显式传type时允许空向量Make(chunks, type)带输入校验的构造MakeEmpty(type, pool)创建指定类型的空 ChunkedArray含一个空数组 chunk。访问接口length()各 chunk 长度之和构造时算好、null_count()全 chunk 空值总数、num_chunks()、chunk(i)/chunks()、Slice(offset, length)零拷贝跨 chunk 边界也能切、Flatten(pool)Struct 类型展开为每个字段一个 ChunkedArray、View(type)对每个 chunk 调用Array::View、GetScalar(index)。此外还有Equals、ToString等常规能力以及用于拼接 chunk 的Concatenateconcatenate.h。从源码结构看ChunkedArray 与 RecordBatch / Table 共同构成了 Arrow 的逻辑数据集层Table 的一列就是一个 ChunkedArray而列式处理引擎compute 模块、dataset/scanner几乎都以 ChunkedArray 为输入输出单位。ArrayVisitor类型分发的访问者模式array.rst最后的 Utilities 一节引用了arrow::ArrayVisitorcpp/src/arrow/visitor.h。它为一每一类具体数组声明了一个虚函数Visit(const XxxArray)覆盖从NullArray、BooleanArray、全部整型/浮点/字符串/二进制/时间/Decimal 数组到 List、Struct、Map、Union、RunEndEncoded、Dictionary、Extension 等所有类型visitor.h#L34-L69。用法分两步先继承ArrayVisitor并重写关心的Visit重载再调用array-Accept(visitor)。Array::Acceptarray_base.h会根据数组的实际类型把调用分派到对应的Visit重载从而避免手写一长串if (type Type::INT32) ... else if ...的类型分派代码。默认的Visit实现返回Status::NotImplemented因此只重写需要处理的类型即可。这是 Arrow 中编写对任意数组类型通用的遍历、序列化、比较逻辑的标准手法。小结一套体系五种层次回到array.rst的组织结构可以把 Arrow C 数组体系归纳为五层ArrayData底层可变内存容器描述 type/length/null_count/offset/buffers/children/dictionaryArray 与中间基类不可变访问层FlatArray→PrimitiveArray划分了定宽与非嵌套逻辑工厂函数MakeArray等从ArrayData/ 标量一键构建具体数组具体子类Primitive/Temporal、Binary-like、Nested、Dictionary、Extension 五大家族对应 cpp/src/arrow/array/ 下的array_primitive.h、array_binary.h、array_nested.h、array_dict.h以及extension_type.h组合与工具ChunkedArray聚合多个 Array 为逻辑大数组ArrayVisitor提供类型安全的分发遍历。要深入验证本文中的每个 API 行为可以在 cpp/src/arrow/array/array_test.cc、array_binary_test.cc、array_dict_test.cc、array_list_test.cc、concatenate_test.cc 等测试文件中找到对应的单元测试用例进一步了解数据写入方向Builder 体系可继续阅读 cpp/src/arrow/array/builder_base.h 及其同目录下的 builder 系列头文件。【免费下载链接】arrowApache Arrow is a multi-language toolbox for accelerated data interchange and in-memory processing项目地址: https://gitcode.com/gh_mirrors/arrow13/arrow创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表