首页/新闻资讯/正文详情

Apache Arrow C++ 行列转换实战:行式数据与列式 Table 的双向转换

发布时间:2026/9/22 19:06:54 来源:云帆数科 栏目:资讯中心
Apache Arrow C++ 行列转换实战:行式数据与列式 Table 的双向转换
Apache Arrow C 行列转换实战行式数据与列式 Table 的双向转换【免费下载链接】arrowApache Arrow is a multi-language toolbox for accelerated data interchange and in-memory processing项目地址: https://gitcode.com/gh_mirrors/arrow12/arrowApache Arrow 的核心理念是列式columnar内存布局——同一字段的所有取值在内存中连续存放从而获得优异的缓存局部性与向量化计算性能。但在真实系统中我们收到的数据往往来自数据库驱动、日志解析器、REST API 等以行式row-wise组织的外部系统。如何高效地把行式数据灌入 Arrow 的列式结构、再从 Arrow 列式结构中还原行式数据是每个 Arrow 集成开发者的必修课。本文基于 Apache Arrow 仓库中的官方示例 docs/source/cpp/examples/row_columnar_conversion.rst 及对应源码系统讲解固定 Schema与动态 Schema两类行列转换场景的完整实现前者以arrow::ArrayBuilder家族为核心后者借助RecordBatchBuilder、VisitTypeInline、类型特征type traits与TableBatchReader四大工具并辅以 RapidJSON 文档互转的完整可运行代码。读完本文你将掌握在 Arrow C 中编写任意 Schema 行列转换器的模式化方法。为什么需要行列转换列式内存 vs 行式数据Arrow 的列式布局如Int64Array中所有值连续存放在一段缓冲区外加独立的有效性位图带来了两大收益向量化CPU 可以对连续内存做 SIMD 批处理跳过无效值只需处理位图零拷贝切片Table可以被切成多个RecordBatch列数据通过指针加偏移复用不产生数据复制。而外部系统JDBC 驱动、JSON/CSV 解析器、RPC 反序列化天然产出行结构例如 C 中的struct或std::vectorstd::map...。因此行列转换器要解决的核心问题就是把以行为粒度遍历的数据按列为粒度写入 Arrow 构建器反向则按列读取后重新组装成行。从源码结构看cpp/examples/arrow 目录Arrow 官方为此提供了两个递进示例覆盖了从编译期已知 Schema到运行时才知 Schema的全谱系场景。固定 Schema 场景struct 数组 ↔ arrow::Table当目标 Schema 在编译期完全确定时可以直接针对具体字段类型使用对应的构建器。官方示例 row_wise_conversion_example.cc 以产品表为业务模型每个产品有一个id、一个组件数量components、以及一组组件单价component_cost。数据模型与构建器选择struct data_row { int64_t id; int64_t components; std::vectordouble component_cost; };针对该结构的三个字段见 row_wise_conversion_example.cc#L39-L43目标列Arrow 类型使用的构建器idint64()arrow::Int64Buildercomponentsint64()arrow::Int64Buildercomponent_costlist(float64())arrow::ListBuilder 内嵌arrow::DoubleBuilder其中component_cost是嵌套列需要两层构建器顶层ListBuilder负责记录每个列表的起始偏移offset内层DoubleBuilder负责存放真正的数值。两个构建器通过value_builder()关联ListBuilder component_cost_builder(pool, std::make_sharedDoubleBuilder(pool)); DoubleBuilder* component_item_cost_builder (static_castDoubleBuilder*(component_cost_builder.value_builder()));这里内层构建器由component_cost_builder拥有因此不要单独调用内层构建器的Finish()——父构建器Finish()时隐含完成子构建器row_wise_conversion_example.cc#L70-L73、#L100-L101。行 → 列逐行 Append、整列 Finish转换函数VectorToColumnarTable的核心流程是创建构建器并传入内存池arrow::default_memory_pool()源码注释说明 Arrow 的 jemalloc 池可原地扩容底层内存区域更高效jemalloc 目前仅支持 Unix 系不支持 Windows见 row_wise_conversion_example.cc#L62-L66逐行 Append对id、components直接Append(value)对列表列先component_cost_builder.Append()标记新列表开始再调用内层构建器的AppendValues(data, size)一次性写入整段 double 数据#L79-L92。所有Append返回arrow::Status必须用ARROW_RETURN_NOT_OK检查——内存分配可能失败Finish 得到 Arrayid_builder.Finish(id_array)等声明 Schema 并组装 Tablearrow::Table::Make(schema, {id_array, components_array, component_cost_array})。Table拥有所有底层数据的所有权函数返回后无需担心悬垂引用#L96-L116。列 → 行Schema 校验与零拷贝偏移反向函数ColumnarTableToVector同样先声明期望的 Schema用expected_schema-Equals(*table-schema())做严格校验不一致时返回Status::Invalid(Schemas are not matching!)row_wise_conversion_example.cc#L128-L137。随后通过std::static_pointer_cast把ChunkedArray的首个 chunk 还原为具体类型Int64Array、ListArray以及ListArray::values()指向的DoubleArray。这里有一个关键陷阱源码注释明确警告由于 Arrow 支持零拷贝切片数组的原始数据指针可能带有一个非零偏移直接使用raw_values()时必须手动加上该偏移否则会读到错误位置const double* ccv_ptr component_cost_values-raw_values(); for (int64_t i 0; i table-num_rows(); i) { const double* first ccv_ptr component_cost-value_offset(i); const double* last ccv_ptr component_cost-value_offset(i 1); std::vectordouble components_vec(first, last); ... }注意高阶访问函数Value(i)内部已自动处理偏移但裸指针raw_values()不会#L149-L170。示例同时假定所有行均非空未处理 null 位图真实场景需结合IsNull()或IsValid()过滤。运行与验证main中构造 3 行原始数据{{1,1,{10.0}}, {2,3,{11.0,12.0,13.0}}, {3,2,{15.0,25.0}}}执行行→表→行往返后用assert校验行数一致并打印表格ID Components Component prices 1 1 10 2 3 11 12 13 3 2 15 25该示例在 cpp/examples/arrow/CMakeLists.txt#L18 中通过add_arrow_example(row_wise_conversion_example)无条件构建无需额外依赖。动态 Schema 场景RapidJSON 文档 ↔ Arrow RecordBatch固定 Schema 方案要求每个字段手写Visit逻辑无法应对Schema 在运行时才确定的通用转换需求例如把 JSON 数组批量导入 Arrow。为此官方提供了第二个示例 rapidjson_row_converter.cc展示了通用转换器的完整骨架其设计正是原文档强调的四大工具组合工具作用arrow::RecordBatchBuilder依据 Schema 一次性创建整批字段的构建器并管理其生命周期arrow::VisitTypeInline依据DataType::id()分发到具体类型特化的Visit()方法arrow::enable_if_primitive_ctype等类型特征用模板约束把具有 C 等价类型的数组bool/int/float合并进一个模板方法arrow::TableBatchReader把 Table 按指定行数切成零拷贝的 RecordBatch 切片逐批读取该示例依赖 RapidJSON因此在 cpp/examples/arrow/CMakeLists.txt#L20-L22 中受ARROW_WITH_RAPIDJSON编译选项门控if(ARROW_WITH_RAPIDJSON) add_arrow_example(rapidjson_row_converter EXTRA_LINK_LIBS RapidJSON) endif()行 → ArrowConvertToRecordBatch 与 JsonValueConverter顶层函数ConvertToRecordBatch(rows, schema)rapidjson_row_converter.cc#L495-L521只有寥寥几步RecordBatchBuilder::Make(schema, default_memory_pool(), rows.size())按 Schema 创建所有字段构建器。传入行数可以让构建器预分配正确容量字符串、字节、列表等变长列除外构造JsonValueConverter converter(rows)遍历 Schema 每个字段converter.Convert(*field, batch_builder-GetField(i))完成一列值的写入batch_builder-Flush()产出RecordBatchbatch-ValidateFull()全量校验数组完整性——对调试新转换实现极有价值能发现偏移、长度、位图不一致等问题。RecordBatchBuilder的完整 API 定义见 cpp/src/arrow/table_builder.h#L39-L105GetField(i)返回基类ArrayBuilder*GetFieldAsT(i)做类型安全的checked_castFlush(reset_builders)支持保留或重置构建器以便循环复用SetInitialCapacity()可调整预分配容量。JsonValueConverterrapidjson_row_converter.cc#L323-L493是这一层的核心它通过arrow::VisitTypeInline(*field.type(), this)把类型分发交给 Arrow 运行时——底层实现是一个基于type.id()的 switch 宏展开每个 case 做checked_cast后调用对应的Visit(const XXXType)见 cpp/src/arrow/visit_type_inline.h#L51-L59。示例实现了Int64Type、DoubleType、StringType、BooleanType、StructType、ListType六种其余类型落入默认Visit(const DataType)返回Status::NotImplemented。每个具体类型的 Visit 逻辑模式完全一致遍历该列在每行中的取值FieldValues()为 null 的行AppendNull()否则把 RapidJSON 值转成对应 C 类型后Append。以Int64Type为例它还处理了 JSON 中Uint/Int/Uint64/Int64多种数值形态的兼容#L352-L373。嵌套类型是真正的难点StructType递归构造子转换器JsonValueConverter(rows, child_path, array_levels_)逐子字段写入StructBuilder::child_builder(i)最后基于整行是否 null 生成 null 位图#L414-L438ListType由于ListBuilder要求先有值、后有偏移代码先用一个临时构建器MakeBuilder(value_type)收集所有元素值再遍历每行用AppendArraySlice把对应偏移区间切片拷入value_builder#L440-L471。FieldValues()私有方法返回一个arrow::Iteratorconst rapidjson::Value*其背后是DocValuesIterator#L231-L321。对于扁平行结构该迭代器实现很简单但 JSON 是多层嵌套的DocValuesIterator通过path要逐级进入的字段名列表与array_levels要穿过的数组层数两个游标加上一个array_stack栈来在嵌套数组间前进/回溯从而把某一列在 N 行中的全部取值压平成一个流。源码注释用一段示例 JSON 直观说明了path与array_levels的对应关系。Arrow → 行ArrowToDocumentConverter 与 RowBatchBuilder反向转换器ArrowToDocumentConverterrapidjson_row_converter.cc#L179-L213把整表转换拆成小批量理由是逐批转换比整表一次转换内存更可控、响应更及时。arrow::Iteratorrapidjson::Document ConvertToIterator( std::shared_ptrarrow::Table table, size_t batch_size) { auto batch_reader std::make_sharedarrow::TableBatchReader(*table); batch_reader-set_chunksize(batch_size); ... }TableBatchReader见 cpp/src/arrow/table.h#L246-L270把Table逐批读出为RecordBatchset_chunksize()设定目标行数上限ReadNext()返回零拷贝切片——不复制任何列数据只切片缓冲区视图。ConvertToVector(batch)转换单个 batch然后依次用arrow::MakeIteratorFromReader、MakeMaybeMapIterator、MakeFlattenIterator把batch 迭代器平铺为Document 迭代器最终使用者既可逐个消费行也可collect成容器。ConvertToVector的逻辑是为 batch 的行数创建RowBatchBuilder构造时预留空间并预置 N 个空的 JSON object逐列SetField后调用arrow::VisitArrayInline(*column, builder)填充每行的字段值。RowBatchBuilder#L57-L177与JsonValueConverter对称但它针对的是Array 类型而非 DataType 类型。其关键技巧正是文档强调的类型特征template typename ArrayType, typename DataClass typename ArrayType::TypeClass arrow::enable_if_primitive_ctypeDataClass, arrow::Status Visit(const ArrayType array) { // 一份模板代码同时覆盖 bool / 整数 / 浮点 ... }arrow::enable_if_primitive_ctype定义于 cpp/src/arrow/type_traits.h#L874-L880其本质是std::is_base_ofPrimitiveCType, T的 SFINAE 开关只有有 C 等价类型的 Arrow 类型boolean、int、float 等才会实例化该模板方法其余类型自动回落到默认Visit(const Array)的NotImplemented分支。这样一行模板代码替换了原本需要重复编写的多个 Visit 方法。字符串、Struct、List 则各自手写特化字符串列用SetString构造 JSON stringStruct 列递归用子RowBatchBuilder填好子行再CopyFrom合并List 列先递归构建全部元素行再按value_length(i)切片组装成 JSON 数组。三个Visit都遵守同一个纪律null 行不写字段仅跳过RapidJSON 中缺失成员即 null。完整的端到端示例与运行参数DoRowConversion(num_rows, batch_size)#L523-L605演示了完整闭环并定义了带嵌套结构的 Schemaauto schema arrow::schema({ arrow::field(pk, arrow::int64()), arrow::field(date_created, arrow::utf8()), arrow::field(data, arrow::struct_({ arrow::field(deleted, arrow::boolean()), arrow::field(metrics, arrow::list(arrow::struct_({ arrow::field(key, arrow::utf8()), arrow::field(value, arrow::int64()), }))), })), });输入数据是三条真实 JSON 记录含空 metrics 数组、多层嵌套 struct/list 等边界形态循环生成num_rows条后写入方向ConvertToRecordBatch→Table::FromRecordBatches→table-ValidateFull()并打印读出方向ConvertToIterator(table, batch_size)逐行还原并断言字段类型与结构完整性再打印 JSON。主函数#L607-L618接受两个命令行参数./rapidjson_row_converter [num_rows100] [batch_size100]编写通用转换器的模式总结综合两个官方示例可以提炼出编写行列转换器的四条通用准则Schema 先行校验兜底转换前必须有目标 Schema可由外部系统提供或像很多系统那样先看前 N 行推断转换后务必ValidateFull()校验写方向与读方向都用Equals校验 Schema 一致性。Schema 在转换过程中动态推断是极具挑战的做法官方建议宁可先取前 N 行推断。类型分发交给VisitTypeInline/VisitArrayInline不要手写巨型if-else类型判断。这两个函数基于type.id()的 switch 完成checked_cast分发cpp/src/arrow/visit_type_inline.h#L51-L59、cpp/src/arrow/visit_array_inline.h#L50-L53访问者只需为关心的类型实现Visit其余走默认NotImplemented。类型特征type traits减少模板膨胀enable_if_primitive_ctypecpp/src/arrow/type_traits.h#L877把行为一致的基本类型合并进一个模板方法需要更多谓词可查阅arrow/type_traits.h中的其它 trait如has_c_type。批量处理、零拷贝切片读出方向不要整表一次转换用TableBatchReader按 batch 处理同时牢记raw_values()裸指针需手动叠加切片偏移而Value(i)等高层接口已内置处理。延伸阅读本文依据的官方文档docs/source/cpp/examples/row_columnar_conversion.rst固定 Schema 完整示例cpp/examples/arrow/row_wise_conversion_example.cc动态 Schema 完整示例cpp/examples/arrow/rapidjson_row_converter.cc核心 API 源码cpp/src/arrow/table_builder.h、cpp/src/arrow/visit_type_inline.h、cpp/src/arrow/visit_array_inline.h、cpp/src/arrow/type_traits.h、cpp/src/arrow/table.h构建配置cpp/examples/arrow/CMakeLists.txtrow_wise_conversion_example无条件构建rapidjson_row_converter需开启ARROW_WITH_RAPIDJSON【免费下载链接】arrowApache Arrow is a multi-language toolbox for accelerated data interchange and in-memory processing项目地址: https://gitcode.com/gh_mirrors/arrow12/arrow创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关推荐

adata源码拆解:3个核心逻辑搞定高频面试题
adata源码拆解:3个核心逻辑搞定高频面试题

adata源码拆解:3个核心逻辑搞定高频面试题 官方文档翻了三遍还是云里雾里?别急,直接看源码。 很多开发者卡在 adata 这类底层数据组件上,不是代码写不出来,而是 抓不住重点… · 2026/9/22 19:06:54

3种关闭445端口的方法源码解析
3种关闭445端口的方法源码解析

3种关闭445端口的方法源码解析 复制来的防火墙规则跑不通,报错 Permission denied 或者端口依然被扫描出来?别急着怀疑环境,多半是你没搞懂底层拦截逻辑。很多教程只给命令,不讲 源码解析 层面的执行机制,导致你在不同… · 2026/9/22 19:06:41

3个坑点教你搞定推广二维码最佳实践
3个坑点教你搞定推广二维码最佳实践

3个坑点教你搞定推广二维码最佳实践 看了一堆教程还是不会写项目,是不是觉得代码跑通了就万事大吉?直到上线那天,用户扫码提示“二维码已过期”或者“链接失效”,你才意识到之前的学习全是纸上谈兵。真正的 最佳实践… · 2026/9/22 19:06:35

vercel CLI 生产日志追踪:`logs --follow` 解析活跃生产部署的实现与使用指南
vercel CLI 生产日志追踪:`logs --follow` 解析活跃生产部署的实现与使用指南

CLI后端云原生 【免费下载链接】vercel Develop. Preview. Ship. 项目地址: https://gitcode.com/gh_mirrors/ve/vercel 点击查看 免费下载 本篇技术指南围绕 Vercel CLI 仓库中一项针对 vercel logs 命令的补丁级变更展开:当用户使用 --follow 跟踪生产… · 2026/9/23 3:05:08

一文搞懂对象、方法、函数与调用:从API报错到跨语言调用的底层逻辑
一文搞懂对象、方法、函数与调用:从API报错到跨语言调用的底层逻辑

前阵子我翻后台留言,发现一个特别有意思的现象:有人卡在“django执行查询后怎么删除对象”,有人报“npm无法识别”,有人问“deepseek api到底怎么调”,还有人被“未将对象引用设置到对象的实例”折磨了一下午。表面上看… · 2026/9/23 3:05:08

工作总结模板从入门到实战
工作总结模板从入门到实战

别再套空模板,3步手写实现你的技术工作总结 看了一堆教程还是不会写项目?这种痛我懂。很多人手里攥着几个开源库,跑通了Demo,但一到要复盘、要晋升、要写年终总结时,脑子就一片空白。这时候, 手写实现… · 2026/9/23 3:05:08

MySQL分区表详解:从分区键设计到查询性能优化实战
MySQL分区表详解:从分区键设计到查询性能优化实战

1. 分区表到底解决了什么问题1.1 分区表不是“优化一切”的银弹我最早接触分区表,是因为线上有张日志表涨到了几千万行,每次按时间范围查数据都要扫半天,索引建了好几组也压不住。后面听人说“分区表能解决大表查询慢”,就直接把表… · 2026/9/23 3:05:08

达西与非达西流动耦合模型:从数学原理到Python实现
达西与非达西流动耦合模型:从数学原理到Python实现

1. 传统达西模型在地下水模拟中的边界困境做了几年地下水数值模拟的人,迟早会撞上同一个疑惑:实测流速和达西定律预测值对不上,而且偏差不是线性的,是那种在高水力梯度下明显偏离线性关系的系统性偏差。我最早意识到这个问题&… · 2026/9/23 3:05:01

植物顺式调控序列演化研究:从基因组到功能验证
植物顺式调控序列演化研究:从基因组到功能验证

1. 植物顺式调控序列的演化全景图当我们在显微镜下观察一片叶子或一朵花时,看到的是静态的结构,但背后却是亿万年来DNA序列不断演化的动态历史。植物基因组中那些不编码蛋白质的调控序列,就像交响乐团的指挥,决定着基因何时、何地… · 2026/9/23 3:04:49

3招搞定手机怎么下载微信面试难题实战项目解析
3招搞定手机怎么下载微信面试难题实战项目解析

3招搞定手机怎么下载微信面试难题实战项目解析 面试被问“手机怎么下载微信”背后的原理,90%的人答不上来。别笑,这看似弱智的问题,实则是考察你对移动应用分发机制、安全校验及网络协议理解的试金石。我带过不少校招新人,他们背了八股文,却连一个A… · 2026/9/23 0:00:03

你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型
你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型

你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 面试被问“高并发下如何保证消息不丢失”,你张口就是“用Redis”,结果面试官追问“如果Redis宕机了怎么办”,你瞬间卡壳。这种场景太常见了,很多新手在背八股文时,只记住了技术名词… · 2026/9/23 0:00:29

Win7无线热点配置工具源码解析:解决API失效的3个实战技巧
Win7无线热点配置工具源码解析:解决API失效的3个实战技巧

Win7无线热点配置工具源码解析:解决API失效的3个实战技巧 Win7无线热点配置工具在Win10/11上跑不动?不是你的问题,是版本升级后 API 全变了。很多老项目里的 netsh wlan… · 2026/9/23 0:00:36

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码