
Apache Arrow C 入门教程从零构建 Array、ChunkedArray、RecordBatch 与 Table【免费下载链接】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 入门文档basic_arrow.rst及其配套完整示例源码arrow_example.cc编写目标是帮助读者掌握 Arrow 的四大基础数据结构Array、ChunkedArray、RecordBatch与Table。文章将以天 / 月 / 年三类整数数据为例完整演示从标准 C 数组到 Arrow 列式结构的构建流程并深入讲解ArrayBuilder、Schema、Field、Status错误处理宏等底层机制。读完本文你将能够独立编写一个可编译运行的 Arrow C 程序并理解这些结构在内存布局、数据复制与行数上限方面的设计差异。预备知识Pre-requisites在继续之前请确保满足以下三个前提已安装 Arrow C 库安装与构建方式参见 C 构建系统指南其中包含 CMake 配置、依赖安装与编译选项的完整说明。仓库中另附一键构建脚本 build_arrow.sh它会通过cmake /arrow/cpp配置并执行make install。熟悉基本 C 数据结构的使用例如标准数组、std::shared_ptr、std::vector等。了解基本 C 数据类型包括int8_t、int16_t等定宽整数类型。环境搭建Setup动手之前需要先补齐两个脚手架引入必要的头文件以及一个把程序粘合起来的main()入口。头文件IncludesArrow 将全部核心 API 汇总在单一头文件arrow/api.h中引入它即可使用数组、构建器、Schema、表格等全部基础能力#include arrow/api.h #include iostream其中iostream用于向终端输出结果。Arrow 示例源码见 arrow_example.cc。main() 入口与错误处理约定Arrow 的 C 库遵循一条重要的设计约定函数不抛出 C 异常而是返回arrow::Status对象。这与 Arrow 在编译时通常禁用异常或仅作为可选特性的 ABI 策略一致。因此常见的程序骨架如下int main() { arrow::Status st RunMain(); if (!st.ok()) { std::cerr st std::endl; return 1; } return 0; }真正的业务逻辑放在RunMain()中它返回arrow::Statusarrow::Status RunMain() { // 全部业务代码写在这里 return arrow::Status::OK(); }这样的结构配合两个关键宏使用就能实现出错即返回、统一上报的简洁错误处理ARROW_RETURN_NOT_OK(expr)若expr返回的Status表示失败则立即从当前函数返回该StatusARROW_ASSIGN_OR_RAISE(lhs, expr)若expr返回的ResultT失败则直接返回错误Status成功则把结果赋值给左侧变量。这两个宏在示例中被大量使用其定义可追溯到 Arrow 核心头文件arrow/result.h、arrow/status.h等实现中。关于错误处理约定的更多细节可阅读 conventions.rst。构建 Arrow 数组Making an Arrow ArrayArrow 保证Array内部各元素在内存中是连续的columnar 布局因此从标准 C 数组迁移到 Arrow 数组不会带来性能损失却可以获得统一的类型系统与后续的计算、序列化能力。构造Array最直接的方式是使用ArrayBuilder构建器家族Array更底层的技术细节可参考 arrays.rst。构建 int8 数组以天day数据为例数据为 8 位有符号整数。首先创建arrow::Int8Builder并用AppendValues()将标准 C 数组的内容灌入构建器arrow::Int8Builder int8builder; int8_t days_raw[5] {1, 12, 17, 23, 28}; ARROW_RETURN_NOT_OK(int8builder.AppendValues(days_raw, 5));AppendValues(ptr, length)会从days_raw起始连续读取 5 个值写入构建器。如果内存分配失败或参数非法宏会带着错误Status直接返回main()并打印失败原因。随后调用Finish()把构建器中的累积数据压成一个真正的Array输出为std::shared_ptrarrow::Arraystd::shared_ptrarrow::Array days; ARROW_ASSIGN_OR_RAISE(days, int8builder.Finish());Finish()返回arrow::Resultstd::shared_ptrArrayARROW_ASSIGN_OR_RAISE负责解包失败则返回错误Status成功则将结果赋给days。关键点Finish()被调用后构建器状态会自动重置如同新构建器一样因此相同类型的数组可以反复复用同一个构建器。下面复用int8builder构建月month数组int8_t months_raw[5] {1, 3, 5, 7, 1}; ARROW_RETURN_NOT_OK(int8builder.AppendValues(months_raw, 5)); std::shared_ptrarrow::Array months; ARROW_ASSIGN_OR_RAISE(months, int8builder.Finish());以上片段对应示例源码 arrow_example.cc 中的(Doc section: int8builder 1 Append)、(Doc section: int8builder 1 Finish)与(Doc section: int8builder 2)三段。构建 int16 数组ArrayBuilder的类型在声明时即被固定之后不可更改。当切换到年year数据时最小需要 16 位整数因此必须新建arrow::Int16Builder方法完全相同只是数据类型不同arrow::Int16Builder int16builder; int16_t years_raw[5] {1990, 2000, 1995, 2000, 1995}; ARROW_RETURN_NOT_OK(int16builder.AppendValues(years_raw, 5)); std::shared_ptrarrow::Array years; ARROW_ASSIGN_OR_RAISE(years, int16builder.Finish());至此我们得到了三个类型各异的 Arrow 数组daysint8、monthsint8、yearsint16。该段对应源码 arrow_example.cc 的(Doc section: int16builder)。从源码结构可以推断Arrow 为每种数据类型都提供了对应的构建器类如Int8Builder、Int16Builder等它们继承自ArrayBuilder基类AppendValues/Finish等核心接口在cpp/src/arrow/array/builder_primitive.h等头文件中声明。构建 RecordBatchMaking a RecordBatch列式格式只有组成表才能真正发挥威力。第一种表格结构是RecordBatch它内部由多个Array组成因此每个列内部是连续内存但任何追加或拼接操作都需要复制数据因为列长度固定且内存连续。给定现成的Array构建RecordBatch分两步先定义Schema再把它与Array一起交给构造函数。定义 SchemaSchema描述列的元信息每一列由一个Field表示Field包含列名与数据类型Schema把多个Field组织起来并确定列的顺序std::shared_ptrarrow::Field field_day, field_month, field_year; std::shared_ptrarrow::Schema schema; field_day arrow::field(Day, arrow::int8()); field_month arrow::field(Month, arrow::int8()); field_year arrow::field(Year, arrow::int16()); schema arrow::schema({field_day, field_month, field_year});这里使用了便捷工厂函数arrow::field(name, type)与arrow::schema(fields)。数据类型对象同样由工厂函数生成arrow::int8()、arrow::int16()。该段对应源码 arrow_example.cc 的(Doc section: Schema)。组装 RecordBatch有了数据Array和列描述Schema就可以构建RecordBatch了。注意列长度是必填参数且所有列长度必须一致std::shared_ptrarrow::RecordBatch rbatch; rbatch arrow::RecordBatch::Make(schema, days-length(), {days, months, years}); std::cout rbatch-ToString();RecordBatch::Make(schema, num_rows, columns)的三个参数分别是Schema、行数取自days-length()、列数组列表。ToString()会把整个表以可读文本形式打印出来方便验证结果。该段对应源码 arrow_example.cc 的(Doc section: RBatch)。构建 ChunkedArrayMaking a ChunkedArrayChunkedArray由若干个子Arraychunk组成设计动机包括拼接零拷贝合并数据时只需维护 chunk 列表无需复制元素便于并行处理不同 chunk 可分派到不同线程缓存友好每个 chunk 可以独立放入 CPU 缓存突破行数上限单个Array的行数受int32_t上限约束2,147,483,647 行而ChunkedArray无此限制。示例中我们复用前面已经构建好的days、months、years再补充几个新数组来凑足多个 chunk从而在不复制已有数据的前提下扩展它们。同样复用之前同类型的构建器int8_t days_raw2[5] {6, 12, 3, 30, 22}; ARROW_RETURN_NOT_OK(int8builder.AppendValues(days_raw2, 5)); std::shared_ptrarrow::Array days2; ARROW_ASSIGN_OR_RAISE(days2, int8builder.Finish()); int8_t months_raw2[5] {5, 4, 11, 3, 2}; ARROW_RETURN_NOT_OK(int8builder.AppendValues(months_raw2, 5)); std::shared_ptrarrow::Array months2; ARROW_ASSIGN_OR_RAISE(months2, int8builder.Finish()); int16_t years_raw2[5] {1980, 2001, 1915, 2020, 1996}; ARROW_RETURN_NOT_OK(int16builder.AppendValues(years_raw2, 5)); std::shared_ptrarrow::Array years2; ARROW_ASSIGN_OR_RAISE(years2, int16builder.Finish());该段对应源码 arrow_example.cc 的(Doc section: More Arrays)。为支持任意数量的子数组参与构造Arrow 提供了ArrayVector即std::vectorstd::shared_ptrArray的类型别名先把数组装进向量arrow::ArrayVector day_vecs{days, days2};再通过ChunkedArray的构造函数把向量封装为列式逻辑数组std::shared_ptrarrow::ChunkedArray day_chunks std::make_sharedarrow::ChunkedArray(day_vecs);对月和年重复相同流程arrow::ArrayVector month_vecs{months, months2}; std::shared_ptrarrow::ChunkedArray month_chunks std::make_sharedarrow::ChunkedArray(month_vecs); arrow::ArrayVector year_vecs{years, years2}; std::shared_ptrarrow::ChunkedArray year_chunks std::make_sharedarrow::ChunkedArray(year_vecs);这三段分别对应源码中的(Doc section: ArrayVector)与(Doc section: ChunkedArray Day)、(Doc section: ChunkedArray Month Year)arrow_example.cc。至此我们得到三个类型各异的ChunkedArray。构建 TableMaking a TableTable是ChunkedArray最典型的应用场景。与RecordBatch类似Table同样存储表格数据但不保证列内连续——因为它由ChunkedArray构成。这种分段结构带来与ChunkedArray相同的收益便于逻辑分块、并行化、缓存友好且能超过Array/RecordBatch的 2,147,483,647 行上限。对比前文可以发现Table的构造函数与RecordBatch几乎相同只是把行数参数放到了第 3 个位置并复用之前的Schemastd::shared_ptrarrow::Table table; table arrow::Table::Make(schema, {day_chunks, month_chunks, year_chunks}, 10); std::cout table-ToString();Table::Make(schema, columns, num_rows)columns是ChunkedArray列表num_rows为 10——即前文每个 chunk 5 行、共 2 个 chunk 的总行数。该段对应源码 arrow_example.cc 的(Doc section: Table)。Table类的完整接口可在 table.h 中查阅。收尾与完整代码在RunMain()末尾返回arrow::Status::OK()通知main()一切正常return arrow::Status::OK();至此你已成功构建了 Arrow 的四大基础数据结构。完整的可编译示例源码位于 cpp/examples/tutorial_examples/arrow_example.cc其内部通过(Doc section: ...)标记将各段代码与本文一一对应方便逐段对照阅读。编译与运行示例仓库为教程示例提供了完整的构建配套便于读者本地复现CMakeLists.txt声明ArrowTutorialExamples工程通过find_package(ArrowDataset)查找已安装的 Arrow以target_link_libraries(arrow_example PRIVATE Arrow::arrow_shared)链接共享库并启用-Wall -Wextra -Werror严格编译与 C20 标准build_arrow.sh先构建并安装 Arrow C 库启用 CSV、Dataset、Filesystem、Parquet 等组件build_example.sh以安装好的 Arrow 为依赖cmake配置示例工程后make编译出arrow_example等可执行文件run.sh串联以上两步并依次运行arrow_example、compute_example、file_access_example、dataset_example。典型流程为先按 build_arrow.sh 安装 Arrow底层对应 C 构建系统指南再执行cmakemake编译arrow_example最后运行该可执行文件观察ToString()输出的表格内容。这些步骤需要在具备 CMake最低 3.25与 C20 编译器如 GCC 11、Clang 14的环境中完成。小结本教程的核心收获可归纳为一张对照表数据结构内部组成列内连续追加/拼接代价行数上限Array单一连续缓冲区是需重建2,147,483,647ChunkedArray多个Array的列表否chunk 间不连续零拷贝无RecordBatch多个Array定长是需复制同ArrayTable多个ChunkedArray否零拷贝无在此基础上下一步可以学习如何将Table/RecordBatch写入文件或从文件读出参见 io_tutorial.rst以及在数组上执行计算内核参见 compute_tutorial.rst与读写多文件分区数据集参见 datasets_tutorial.rst。【免费下载链接】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),仅供参考