陣列#
另請參閱
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()) {
// ... 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
)只是為了方便起見提供的 typedef
,分別為 arrow::NumericArray<Int64Type>
(或 arrow::NumericBuilder<Int64Type>
)。
效能#
雖然可以像上面的範例一樣逐值建立陣列,但為了獲得最高效能,建議在具體的 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();
如果您仍然必須逐個附加值,則某些具體建構器子類別具有標記為「Unsafe」的方法,這些方法假設工作區已正確預先調整大小,並提供更高的效能作為交換
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()
方法來完成的。