关于arrow::ListArray::offsets()含零及偏移量使用的技术问询
C++读取含浮点型列表的Parquet文件时遇到的偏移量异常问题
问题重现步骤
- 通过Python(Polars+PyArrow)生成包含10行浮点型列表数据的测试Parquet文件
- 使用C++ Arrow库读取该文件时,发现
arrow::ListArray::offsets()返回的偏移量列表中穿插零值 - 按常规示例调用
value_offset(i)会导致程序崩溃,改用value_offset(2*i)才能正确读取数据
技术疑问
- 为何偏移量列表中会穿插零值?
- 为何文档说明偏移量数量为
num_rows+1,但实际需访问到2*num_rows才能获取全部数据? - 该现象是否为Arrow+Parquet的固定表现?十万行级数据是否仍遵循此模式?
问题解答
1. 偏移量列表穿插零值的原因
这是因为你错误读取了非偏移量的内存数据,大概率是混淆了ListArray的偏移量数组和有效性位图的存储。当你用Polars/PyArrow生成可空列表列时,Arrow会为该列额外维护一个有效性位图(标记每行是否为空值),如果你的C++代码没有通过正确API单独提取偏移量数组,而是直接读取了列的全部底层buffer,就会把位图的二进制零值当成偏移量的一部分,出现穿插零值的假象。
另外,如果某行是空值(而非空列表),Arrow会通过有效性位图标记,而非修改偏移量;但如果你误将位图数据混入偏移量读取,就会看到无意义的零值。
2. 偏移量数量与文档不符的原因
文档说明的num_rows+1是完全正确的,你需要访问到2*num_rows才能获取数据,本质是读取范围错误:你把有效性位图的长度也算进了偏移量数组的长度里。比如10行的ListArray,偏移量数组长度是11(10+1),而有效性位图按字节对齐后可能占用10字节左右,如果你把这两部分数据合并读取,就会误以为偏移量数组长度接近20,进而需要访问到2*num_rows。
3. 是否为固定表现及大数据量情况
这不是Arrow+Parquet的固定表现,完全是读取逻辑错误导致的。十万行数据下,只要正确使用Arrow C++的API读取偏移量,就不会出现这种问题。
正确的读取示例代码:
// 假设已读取到arrow::Array类型的列数据 auto list_array = std::static_pointer_cast<arrow::ListArray>(column_data); // 获取正确的偏移量数组(注意类型为Int32Array或Int64Array) auto offsets = std::static_pointer_cast<arrow::Int32Array>(list_array->offsets()); // 遍历每行数据 for (int64_t i = 0; i < list_array->length(); ++i) { // 检查当前行是否为有效非空行 if (!list_array->IsValid(i)) { // 处理空值逻辑 continue; } int32_t start = offsets->Value(i); int32_t end = offsets->Value(i+1); // 获取列表元素数组 auto values = std::static_pointer_cast<arrow::FloatArray>(list_array->values()); // 遍历当前行的所有元素 for (int32_t j = start; j < end; ++j) { float val = values->Value(j); // 处理元素逻辑 } }
同时建议检查Python生成代码,确保列表列类型为非可空(如Polars中的List(f64)而非Optional[List(f64)]),这样Parquet写入时不会生成有效性位图,能进一步简化读取逻辑。
内容的提问来源于stack exchange,提问作者vinnewbie
相关产品推荐
相关产品推荐

