数组#

另请参阅

数组 API 参考

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

这些缓冲区包含值数据本身以及一个可选的位图缓冲区,该缓冲区指示哪些数组条目是空值。如果已知数组没有空值,则可以完全省略位图缓冲区。

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

构建数组#

可用策略#

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

  • 如果数据以正确的布局已经存在于内存中,您可以将所述内存包装在 arrow::Buffer 实例中,然后构造一个描述数组的 arrow::ArrayData

    另请参阅

    内存管理

  • 否则,arrow::ArrayBuilder 基类及其具体子类有助于增量构建数组数据,而无需您自己处理 Arrow 格式的细节。

注意

对于性能不重要的场景,例如示例或测试,您可能更喜欢使用 *FromJSONString 辅助函数,它可以使用 JSON 文本简写创建数组。请参阅 FromJSONString 辅助函数

使用 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()) {
   // ... do something on array building failure
}
std::shared_ptr<arrow::Array> array = *maybe_array;

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

以下是您访问具体数组内容的方法:

// Cast the Array to its actual type to access its data
auto int64_array = std::static_pointer_cast<arrow::Int64Array>(array);

// Get the pointer to the null bitmap
const uint8_t* null_bitmap = int64_array->null_bitmap_data();

// Get the pointer to the actual data
const int64_t* data = int64_array->raw_values();

// Alternatively, given an array index, query its null bit and value directly
int64_t index = 2;
if (!int64_array->IsNull(index)) {
   int64_t value = int64_array->Value(index);
}

注意

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

性能#

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

如果您提前知道元素的数量,还建议通过调用 Resize()Reserve() 方法来预设工作区大小。

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

arrow::Int64Builder builder;
// Make place for 8 values in total
builder.Reserve(8);
// Bulk append the given values (with a null in 4th place as indicated by the
// validity vector)
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();

如果您仍然必须逐个追加值,一些具体的构建器子类具有标记为“不安全”的方法,这些方法假定工作区已正确预设大小,并以更高的性能作为交换。

arrow::Int64Builder builder;
// Make place for 8 values in total
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++ 实现中最多可容纳 2^63 个元素,但其他 Arrow 实现也可能对这些数组类型具有 32 位大小限制。

基于这些原因,建议将大数据分块成更合理大小的子集。

分块数组#

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

分块数组通过聚合任意数量的数组来构造。在这里,我们将构建一个分块数组,其逻辑值与上述示例相同,但分为两个独立的块:

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

// Build first chunk
arrow::Int64Builder builder;
builder.Append(1);
builder.Append(2);
builder.Append(3);
if (!builder.Finish(&array).ok()) {
   // ... do something on array building failure
}
chunks.push_back(std::move(array));

// Build second chunk
builder.Reset();
builder.AppendNull();
builder.Append(5);
builder.Append(6);
builder.Append(7);
builder.Append(8);
if (!builder.Finish(&array).ok()) {
   // ... do something on array building failure
}
chunks.push_back(std::move(array));

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

assert(chunked_array->num_chunks() == 2);
// Logical length in number of values
assert(chunked_array->length() == 8);
assert(chunked_array->null_count() == 1);

切片#

与物理内存缓冲区一样,可以对数组和分块数组进行零拷贝切片,以获得引用数据中某个逻辑子序列的数组或分块数组。这分别通过调用 arrow::Array::Slice()arrow::ChunkedArray::Slice() 方法来完成。

FromJSONString 辅助函数#

提供了一组辅助函数,用于从 JSON 文本中简洁地创建数组和标量。这些辅助函数旨在用于示例、测试或快速原型设计,不适用于性能至关重要的场景。大多数用户会希望使用 读取 JSON 文件 中描述的 API,该 API 提供了一种高性能的方式来从行分隔的 JSON 文件创建 arrow::Tablearrow::RecordBatch 对象。

下面显示了 ArrayFromJSONStringChunkedArrayFromJSONStringDictArrayFromJSONString 的示例:

// Simple types
ARROW_ASSIGN_OR_RAISE(auto int32_array,
                      ArrayFromJSONString(arrow::int32(), "[1, 2, 3]"));
ARROW_ASSIGN_OR_RAISE(auto float64_array,
                      ArrayFromJSONString(arrow::float64(), "[4.0, 5.0, 6.0]"));
ARROW_ASSIGN_OR_RAISE(auto bool_array,
                      ArrayFromJSONString(arrow::boolean(), "[true, false, true]"));
ARROW_ASSIGN_OR_RAISE(
    auto string_array,
    ArrayFromJSONString(arrow::utf8(), R"(["Hello", "World", null])"));

// Timestamps can be created from string representations
ARROW_ASSIGN_OR_RAISE(
    auto ts_array,
    ArrayFromJSONString(timestamp(arrow::TimeUnit::SECOND),
                        R"(["1970-01-01", "2000-02-29","3989-07-14","1900-02-28"])"));

// List, Map, Struct
ARROW_ASSIGN_OR_RAISE(
    auto list_array,
    ArrayFromJSONString(list(arrow::int64()),
                        "[[null], [], null, [4, 5, 6, 7, 8], [2, 3]]"));
ARROW_ASSIGN_OR_RAISE(
    auto map_array,
    ArrayFromJSONString(map(arrow::utf8(), arrow::int32()),
                        R"([[["joe", 0], ["mark", null]], null, [["cap", 8]], []])"));
ARROW_ASSIGN_OR_RAISE(
    auto struct_array,
    ArrayFromJSONString(
        arrow::struct_({field("one", arrow::int32()), field("two", arrow::int32())}),
        "[[11, 22], null, [null, 33]]"));

// ChunkedArrayFromJSONString
ARROW_ASSIGN_OR_RAISE(
    auto chunked_array,
    ChunkedArrayFromJSONString(arrow::int32(), {"[5, 10]", "[null]", "[16]"}));

// DictArrayFromJSONString
ARROW_ASSIGN_OR_RAISE(
    auto dict_array,
    DictArrayFromJSONString(dictionary(arrow::int32(), arrow::utf8()),
                            "[0, 1, 0, 2, 0, 3]", R"(["k1", "k2", "k3", "k4"])"));

有关完整的辅助函数集,请参阅 FromJSONString API 列表