arrow系列14---数组(Arrays)
作者:yunjinqi   类别:    日期:2023-10-15 20:20:47    阅读:130 次   消耗积分:0 分    

Arrow中的核心类型是arrow::Array类。数组代表具有相同类型的已知长度值的序列。在内部,这些值由一个或多个缓冲区表示,缓冲区的数量和含义取决于数组的数据类型,如Arrow数据布局规范中所记录。

这些缓冲区包括值数据本身和一个可选的位图缓冲区,用于指示哪些数组条目是空值。如果已知数组不包含任何空值,可以完全省略位图缓冲区。

对于每种数据类型,都有arrow::Array的具体子类,可帮助您访问数组的各个值。

构建数组 

可用的策略 

由于Arrow对象是不可变的,无法像std::vector一样直接填充它们。相反,可以使用以下几种策略:

如果数据已经存在于具有正确布局的内存中,可以将该内存包装在arrow::Buffer实例中,然后构建描述数组的arrow::ArrayData。

请参见 内存管理

否则,arrow::ArrayBuilder基类及其具体子类可帮助逐步构建数组数据,而无需自行处理Arrow格式的详细信息。

使用ArrayBuilder及其子类 要构建一个Int64 Arrow数组,可以使用arrow::Int64Builder类。在下面的示例中,我们构建一个范围为1到8的数组,其中应为空值的元素是4:

arrow::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()) {
   // ...在数组构建失败时执行某些操作
}
std::shared_ptr<arrow::Array> array = *maybe_array;

结果的Array(如果要访问其值,可以将其转换为具体的arrow::Int64Array子类)由两个arrow::Buffers组成。第一个缓冲区包含位图,这里由一个字节表示,位为1|1|1|1|0|1|1|1。因为使用最低有效位(LSB)编号,这表示数组中的第四个条目为空值。第二个缓冲区只是包含上述所有值的int64_t数组。由于第四个条目为空,缓冲区中的该位置的值未定义。

以下是如何访问具体数组内容的示例:

// 将Array强制转换为实际类型以访问其数据
auto int64_array = std::static_pointer_castarrow::Int64Array(array);
// 获取位图的指针
const uint8_t* null_bitmap = int64_array->null_bitmap_data();
// 获取实际数据的指针
const int64_t* data = int64_array->raw_values();
// 或者,根据数组索引,可以直接查询其空位和值
int64_t index = 2;
if (!int64_array->IsNull(index)) {
int64_t value = int64_array->Value(index);
}

注意

arrow::Int64Array(以及arrow::Int64Builder)只是为方便而提供的typedef,它们是arrow::NumericArray<Int64Type>(以及arrow::NumericBuilder<Int64Type>)的别名。

性能 

虽然可以像上面的示例一样逐个值构建数组,但为了获得最佳性能,建议使用具体的arrow::ArrayBuilder子类中通常称为AppendValues的批量附加方法。

如果事先知道元素的数量,还建议通过调用Resize()或Reserve()方法来为工作区预分配空间。

以下是如何重写上面的示例以利用这些API的方法:

arrow::Int64Builder builder;
// 总共为8个值预留位置
builder.Reserve(8);
// 批量附加给定的值(使用有效性向量指示第4个位置为空值)
std::vector<bool> validity = {true, true, true, false, true, true, true, true};
std::vector<int64_t> values = {1, 2, 3, 0, 5, 6, 7, 8};
builder.AppendValues(values, validity);

auto maybe_array = builder.Finish();

如果仍然必须逐个追加值,某些具体的构建器子类具有标记为“Unsafe”的方法,这些方法假定工作区已正确预分配,并提供更高的性能:

arrow::Int64Builder builder;
// 总共为8个值预留位置
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();

大小限制和建议 

某些数组类型在结构上受到32位大小的限制。这适用于列表数组(最多可容纳2^31个元素)、字符串数组和二进制数组(最多可容纳2GB的二进制数据),至少在C++实现中是如此。其他一些数组类型在C++实现中最多可以容纳2^63个元素,但其他Arrow实现也可能对这些数组类型有32位大小的限制。

因此,建议将大型数据分块处理,分成更合理大小的子集。


分块数组

arrow::ChunkedArray与数组一样,它是一系列值的逻辑序列;但与简单数组不同,分块数组不需要在内存中完全连续。此外,分块数组的组成部分不必具有相同的大小,但它们必须具有相同的数据类型。

通过汇总任意数量的数组可以构建分块数组。在下面的示例中,我们将构建一个包含与上面示例中相同逻辑值的分块数组,但分为两个单独的块:

std::vector<std::shared_ptr<arrow::Array>> chunks;
std::shared_ptr<arrow::Array> array;

// 构建第一个块
arrow::Int64Builder builder;
builder.Append(1);
builder.Append(2);
builder.Append(3);
if (!builder.Finish(&array).ok()) {
   // ...在数组构建失败时执行某些操作
}
chunks.push_back(std::move(array));

// 构建第二个块
builder.Reset();
builder.AppendNull();
builder.Append(5);
builder.Append(6);
builder.Append(7);
builder.Append(8);
if (!builder.Finish(&array).ok()) {
   // ...在数组构建失败时执行某些操作
}
chunks.push_back(std::move(array));

auto chunked_array = std::make_shared<arrow::ChunkedArray>(std::move(chunks));

assert(chunked_array->num_chunks() == 2);
// 逻辑长度,按值的数量
assert(chunked_array->length() == 8);
assert(chunked_array->null_count() == 1);


切片 

与物理内存缓冲区一样,可以通过调用arrow::Array::Slice()和arrow::ChunkedArray::Slice()方法,创建零拷贝的数组和分块数组的切片,以获取指向数据的逻辑子序列的数组或分块数组。

版权所有,转载本站文章请注明出处:云子量化, http://www.woniunote.com/article/347
上一篇:arrow系列13---内存管理(Memory Management)
下一篇:arrow系列15---数据类型(Data Types)