数据类型#

另请参阅

数据类型 API 参考.

数据类型决定了如何解释物理数据。它们的规范允许不同的 Arrow 实现之间进行二进制互操作,包括来自不同编程语言和运行时(例如,可以使用 pyarrow.jvm 桥接模块从 Python 和 Java 访问相同的数据,而无需复制)。

在 C++ 中,可以使用三种方式表示数据类型信息

  1. 使用 arrow::DataType 实例(例如,作为函数参数)

  2. 使用 arrow::DataType 具体子类(例如,作为模板参数)

  3. 使用 arrow::Type::type 枚举值(例如,作为 switch 语句的条件)

第一种形式(使用 arrow::DataType 实例)是最惯用和灵活的。运行时参数类型只能用 DataType 实例完全表示。例如,arrow::TimestampType 需要在运行时使用 arrow::TimeUnit::type 参数构造;arrow::Decimal128Type 需要使用 *scale* 和 *precision* 参数构造;arrow::ListType 需要使用完整的子类型(本身是一个 arrow::DataType 实例)构造。

为了避免付出动态类型和多态性的代价,可以在性能至关重要的场景中使用其他两种形式。但是,对于参数类型,仍然需要进行一些运行时切换。由于 Arrow 数据类型允许任意嵌套,因此不可能在编译时具体化所有可能的类型。

创建数据类型#

要实例化数据类型,建议调用提供的 工厂函数

std::shared_ptr<arrow::DataType> type;

// A 16-bit integer type
type = arrow::int16();
// A 64-bit timestamp type (with microsecond granularity)
type = arrow::timestamp(arrow::TimeUnit::MICRO);
// A list type of single-precision floating-point values
type = arrow::list(arrow::float32());

类型特征#

如果需要处理具体的arrow::DataType子类,那么编写代码将会非常冗长,除非使用类型特征。Arrow 的类型特征将 Arrow 数据类型映射到专门的数组、标量、构建器和其他关联类型。例如,布尔类型的特征如下:

template <>
struct TypeTraits<BooleanType> {
  using ArrayType = BooleanArray;
  using BuilderType = BooleanBuilder;
  using ScalarType = BooleanScalar;
  using CType = bool;

  static constexpr int64_t bytes_required(int64_t elements) {
    return bit_util::BytesForBits(elements);
  }
  constexpr static bool is_parameter_free = true;
  static inline std::shared_ptr<DataType> type_singleton() { return boolean(); }
};

有关这些字段的说明,请参阅 类型特征

使用类型特征,可以编写可以处理各种 Arrow 类型的模板函数。例如,要编写一个函数来为任何 Arrow 数字类型创建斐波那契值数组:

template <typename DataType,
          typename BuilderType = typename arrow::TypeTraits<DataType>::BuilderType,
          typename ArrayType = typename arrow::TypeTraits<DataType>::ArrayType,
          typename CType = typename arrow::TypeTraits<DataType>::CType>
arrow::Result<std::shared_ptr<ArrayType>> MakeFibonacci(int32_t n) {
  BuilderType builder;
  CType val = 0;
  CType next_val = 1;
  for (int32_t i = 0; i < n; ++i) {
    builder.Append(val);
    CType temp = val + next_val;
    val = next_val;
    next_val = temp;
  }
  std::shared_ptr<ArrayType> out;
  ARROW_RETURN_NOT_OK(builder.Finish(&out));
  return out;
}

对于一些常见情况,类本身就有关联类型。可以使用:

  • Scalar::TypeClass 获取标量的数据类型类

  • Array::TypeClass 获取数组的数据类型类

  • DataType::c_type 获取 Arrow 数据类型的关联 C 类型

std::type_traits 中提供的类型特征类似,Arrow 提供了类型谓词,例如 is_number_type,以及包装 std::enable_if_t 的相应模板,例如 enable_if_number。这些可以将模板函数限制为仅针对相关类型进行编译,如果需要实现其他重载,这将非常有用。例如,要为任何数字(整数或浮点数)数组编写一个求和函数:

template <typename ArrayType, typename DataType = typename ArrayType::TypeClass,
          typename CType = typename DataType::c_type>
arrow::enable_if_number<DataType, CType> SumArray(const ArrayType& array) {
  CType sum = 0;
  for (std::optional<CType> value : array) {
    if (value.has_value()) {
      sum += value.value();
    }
  }
  return sum;
}

有关这些内容的列表,请参阅 类型谓词

访问者模式#

为了处理 arrow::DataTypearrow::Scalararrow::Array,您可能需要编写基于特定 Arrow 类型的专门逻辑。在这种情况下,请使用 访问者模式。Arrow 提供了以下模板函数:

要使用这些函数,请为每个专门类型实现 Status Visit() 方法,然后将类实例传递给内联访问函数。为了避免重复代码,请使用上一节中介绍的类型特征。作为一个简单的示例,以下是如何对任意数字类型的列求和:

class TableSummation {
  double partial = 0.0;
 public:

  arrow::Result<double> Compute(std::shared_ptr<arrow::RecordBatch> batch) {
    for (std::shared_ptr<arrow::Array> array : batch->columns()) {
      ARROW_RETURN_NOT_OK(arrow::VisitArrayInline(*array, this));
    }
    return partial;
  }

  // Default implementation
  arrow::Status Visit(const arrow::Array& array) {
    return arrow::Status::NotImplemented("Cannot compute sum for array of type ",
                                         array.type()->ToString());
  }

  template <typename ArrayType, typename T = typename ArrayType::TypeClass>
  arrow::enable_if_number<T, arrow::Status> Visit(const ArrayType& array) {
    for (std::optional<typename T::c_type> value : array) {
      if (value.has_value()) {
        partial += static_cast<double>(value.value());
      }
    }
    return arrow::Status::OK();
  }
};

Arrow 还提供了抽象访问者类(arrow::TypeVisitorarrow::ScalarVisitorarrow::ArrayVisitor)和每个相应基类型上的 Accept() 方法(例如 arrow::Array::Accept())。但是,这些方法无法使用模板函数实现,因此您通常更喜欢使用内联类型访问者。