Spark sql schema StructField中metadata原理用法源码分析

Spark sql schema StructField中metadata源码分析


Metadata 是 Scala 中的一个密封类(sealed class),它用于在 Spark 中存储和传递数据结构的元数据信息。密封类是一种特殊的类,它可以有子类,但这些子类必须在同一个文件中定义。这使得 Metadata 类的继承关系在编译时是完全可控的。

原理

下面是 Metadata 类的源代码分析:

sealed class Metadata(val map: Map[String, Any]) extends Serializable

Metadata接受一个 Map 对象作为构造函数参数,并将其保存在 map 成员变量中。Map 中的键是字符串类型,值可以是任意类型的对象。由于 Metadata 类是密封的,所以不能直接创建该类的实例。相反,需要使用 MetadataBuilder 类来构建 Metadata 实例。

Metadata 类的主要用途是存储和传递数据结构的元数据信息。元数据可以包含各种类型的键值对,用于描述数据结构的属性、类型、格式等。通过使用 MetadataBuilder,我们可以方便地构建包含元数据的 Metadata 实例。

用法

以下是 Metadata 类的用法总结:

  • 创建 Metadata 实例:由于 Metadata 是一个密封类,不能直接创建实例。需要使用 MetadataBuilder 来构建 Metadata 实例。
  • 构建元数据:使用 MetadataBuilder 的各种 put 方法来添加键值对,构建元数据对象。
  • 合并元数据:使用 withMetadata 方法,可以将一个 Metadata 实例的内容包含到另一个 MetadataBuilder 中,实现元数据的合并。
  • 移除键值对:使用 remove 方法可以从 MetadataBuilder 中移除指定的键值对。
  • 构建不可变的元数据:调用 build 方法,将当前 MetadataBuilder 中的键值对构建成一个不可变的 Metadata 实例。
  • 获取元数据的 Map 表示:通过 map 成员变量可以获取元数据的键值对的 Map 表示。

总结起来,Metadata 类提供了一种便捷的方式来存储和传递数据结构的元数据信息。它通过 MetadataBuilder 类来构建和操作元数据,并提供了丰富的方法来添加、合并、移除键值对等操作。使用 Metadata 可以更好地描述和处理数据结构的属性和特征,为数据处理提供更多的上下文信息。

示例

以下是一个使用 Metadata 的示例:

import org.apache.spark.sql.types._

// 创建一个空的 MetadataBuilder 对象
val builder = new MetadataBuilder()

// 添加元数据键值对
builder.putString("name", "John Doe")
builder.putLong("age", 30)
builder.putBoolean("isStudent", false)

// 构建 Metadata 实例
val metadata = builder.build()

// 输出元数据的 map 表示
println(metadata)

运行以上代码,输出结果如下:

{"isStudent":false,"age":30,"name":"John Doe"}

中文源码

class Metadata

/**
 * Metadata是Map[String, Any]的包装类,限制了值类型为简单类型: Boolean、Long、Double、String、Metadata、Array[Boolean]、Array[Long]、Array[Double]、Array[String]和Array[Metadata]。使用JSON进行序列化。
 *
 * 默认构造函数是私有的。用户应该使用[[MetadataBuilder]]或`Metadata.fromJson()`来创建Metadata实例。
 *
 * @param map 存储数据的不可变映射
 *
 * @since 1.3.0
 */
@InterfaceStability.Stable
sealed class Metadata private[types] (private[types] val map: Map[String, Any])
  extends Serializable {

  /** Kryo的无参构造函数 */
  protected def this() = this(null)

  /** 检查此Metadata是否包含指定键 */
  def contains(key: String): Boolean = map.contains(key)

  /** 获取Long类型的值 */
  def getLong(key: String): Long = get(key)

  /** 获取Double类型的值 */
  def getDouble(key: String): Double = get(key)

  /** 获取Boolean类型的值 */
  def getBoolean(key: String): Boolean = get(key)

  /** 获取String类型的值 */
  def getString(key: String): String = get(key)

  /** 获取Metadata类型的值 */
  def getMetadata(key: String): Metadata = get(key)

  /** 获取Long数组类型的值 */
  def getLongArray(key: String): Array[Long] = get(key)

  /** 获取Double数组类型的值 */
  def getDoubleArray(key: String): Array[Double] = get(key)

  /** 获取Boolean数组类型的值 */
  def getBooleanArray(key: String): Array[Boolean] = get(key)

  /** 获取String数组类型的值 */
  def getStringArray(key: String): Array[String] = get(key)

  /** 获取Metadata数组类型的值 */
  def getMetadataArray(key: String): Array[Metadata] = get(key)

  /** 转换为JSON表示形式 */
  def json: String = compact(render(jsonValue))

  override def toString: String = json

  override def equals(obj: Any): Boolean = {
    obj match {
      case that: Metadata if map.size == that.map.size =>
        map.keysIterator.forall { key =>
          that.map.get(key) match {
            case Some(otherValue) =>
              val ourValue = map.get(key).get
              (ourValue, otherValue) match {
                case (v0: Array[Long], v1: Array[Long]) => java.util.Arrays.equals(v0, v1)
                case (v0: Array[Double], v1: Array[Double]) => java.util.Arrays.equals(v0, v1)
                case (v0: Array[Boolean], v1: Array[Boolean]) => java.util.Arrays.equals(v0, v1)
                case (v0: Array[AnyRef], v1: Array[AnyRef]) => java.util.Arrays.equals(v0, v1)
                case (v0, v1) => v0 == v1
              }
            case None => false
          }
        }
      case other =>
        false
    }
  }

  private lazy val _hashCode: Int = Metadata.hash(this)
  override def hashCode: Int = _hashCode

  private def get[T](key: String): T = {
    map(key).asInstanceOf[T]
  }

  private[sql] def jsonValue: JValue = Metadata.toJsonValue(this)
}

object Metadata

/**
 * @since 1.3.0
 */
@InterfaceStability.Stable
object Metadata {

  private[this] val _empty = new Metadata(Map.empty)

  /** 返回一个空的Metadata实例 */
  def empty: Metadata = _empty

  /** 从JSON创建Metadata实例 */
  def fromJson(json: String): Metadata = {
    fromJObject(parse(json).asInstanceOf[JObject])
  }

  /** 
   * 从JSON AST创建Metadata实例。
   */
  private[sql] def fromJObject(jObj: JObject): Metadata = {
    val builder = new MetadataBuilder
    jObj.obj.foreach {
      case (key, JInt(value)) =>
        builder.putLong(key, value.toLong) // 将Long类型的值放入MetadataBuilder中
      case (key, JDouble(value)) =>
        builder.putDouble(key, value) // 将Double类型的值放入MetadataBuilder中
      case (key, JBool(value)) =>
        builder.putBoolean(key, value) // 将Boolean类型的值放入MetadataBuilder中
      case (key, JString(value)) =>
        builder.putString(key, value) // 将String类型的值放入MetadataBuilder中
      case (key, o: JObject) =>
        builder.putMetadata(key, fromJObject(o)) // 将嵌套的JSON对象转换为Metadata并放入MetadataBuilder中
      case (key, JArray(value)) =>
        if (value.isEmpty) {
          // 如果是空数组,无法推断其元素类型。我们将放入一个空的Array[Long]。
          builder.putLongArray(key, Array.empty)
        } else {
          value.head match {
            case _: JInt =>
              builder.putLongArray(key, value.asInstanceOf[List[JInt]].map(_.num.toLong).toArray) // 将Long类型的数组放入MetadataBuilder中
            case _: JDouble =>
              builder.putDoubleArray(key, value.asInstanceOf[List[JDouble]].map(_.num).toArray) // 将Double类型的数组放入MetadataBuilder中
            case _: JBool =>
              builder.putBooleanArray(key, value.asInstanceOf[List[JBool]].map(_.value).toArray) // 将Boolean类型的数组放入MetadataBuilder中
            case _: JString =>
              builder.putStringArray(key, value.asInstanceOf[List[JString]].map(_.s).toArray) // 将String类型的数组放入MetadataBuilder中
            case _: JObject =>
              builder.putMetadataArray(
                key, value.asInstanceOf[List[JObject]].map(fromJObject).toArray) // 将嵌套的JSON对象数组转换为Metadata数组并放入MetadataBuilder中
            case other =>
              throw new RuntimeException(s"不支持类型为${other.getClass}的数组。")
          }
        }
      case (key, JNull) =>
        builder.putNull(key) // 将null值放入MetadataBuilder中
      case (key, other) =>
        throw new RuntimeException(s"不支持类型为${other.getClass}的值。")
    }
    builder.build() // 构建Metadata实例
  }

  /** 
   * 转换为JSON AST。
   */
  private def toJsonValue(obj: Any): JValue = {
    obj match {
      case map: Map[_, _] =>
        val fields = map.toList.map { case (k, v) => (k.toString, toJsonValue(v)) } // 将Map转换为JObject
        JObject(fields)
      case arr: Array[_] =>
        val values = arr.toList.map(toJsonValue) // 递归地将数组元素转换为JValue
        JArray(values) // 将List[JValue]转换为JArray
      case x: Long =>
        JInt(x) // 将Long转换为JInt
      case x: Double =>
        JDouble(x) // 将Double转换为JDouble
      case x: Boolean =>
        JBool(x) // 将Boolean转换为JBool
      case x: String =>
        JString(x) // 将String转换为JString
      case null =>
        JNull // 将null转换为JNull
      case x: Metadata =>
        toJsonValue(x.map) // 递归地将Metadata的map属性转换为JValue
      case other =>
        throw new RuntimeException(s"不支持类型为${other.getClass}的值。")
    }
  }

  /** 
   * 计算我们支持的类型的哈希码。
   */
  private def hash(obj: Any): Int = {
    obj match {
      case map: Map[_, _] =>
        // 如果是Map类型,则计算每个值的哈希码并取并集
        map.mapValues(hash).##
      case arr: Array[_] =>
        // 如果是Array类型,则转换为Seq,再计算每个元素的哈希码并取并集
        // Seq.empty[T]的hashCode与T的类型无关
        arr.toSeq.map(hash).##
      case x: Long =>
        // 如果是Long类型,则直接计算其哈希码
        x.##
      case x: Double =>
        // 如果是Double类型,则直接计算其哈希码
        x.##
      case x: Boolean =>
        // 如果是Boolean类型,则直接计算其哈希码
        x.##
      case x: String =>
        // 如果是String类型,则直接计算其哈希码
        x.##
      case x: Metadata =>
        // 如果是Metadata类型,则递归计算其map属性的哈希码
        hash(x.map)
      case null =>
        // 如果是null,则返回0
        0
      case other =>
        // 如果是其他类型,则抛出异常,表示不支持该类型
        throw new RuntimeException(s"不支持的类型 ${other.getClass}.")
    }
  }
}

MetadataBuilder

/**
 * [[Metadata]]的构建器。如果有键冲突,后者将覆盖前者。
 *
 * @since 1.3.0
 */
@InterfaceStability.Stable
class MetadataBuilder {

  private val map: mutable.Map[String, Any] = mutable.Map.empty

  /** 返回该map的不可变版本。用于Java交互。 */
  protected def getMap = map.toMap

  /** 包含现有[[Metadata]]实例的内容。 */
  def withMetadata(metadata: Metadata): this.type = {
    // 将metadata的map属性合并到当前map中
    map ++= metadata.map
    this
  }

  /** 放入一个null值。 */
  def putNull(key: String): this.type = put(key, null)

  /** 放入一个Long值。 */
  def putLong(key: String, value: Long): this.type = put(key, value)

  /** 放入一个Double值。 */
  def putDouble(key: String, value: Double): this.type = put(key, value)

  /** 放入一个Boolean值。 */
  def putBoolean(key: String, value: Boolean): this.type = put(key, value)

  /** 放入一个String值。 */
  def putString(key: String, value: String): this.type = put(key, value)

  /** 放入一个[[Metadata]]值。 */
  def putMetadata(key: String, value: Metadata): this.type = put(key, value)

  /** 放入一个Long数组。 */
  def putLongArray(key: String, value: Array[Long]): this.type = put(key, value)

  /** 放入一个Double数组。 */
  def putDoubleArray(key: String, value: Array[Double]): this.type = put(key, value)

  /** 放入一个Boolean数组。 */
  def putBooleanArray(key: String, value: Array[Boolean]): this.type = put(key, value)

  /** 放入一个String数组。 */
  def putStringArray(key: String, value: Array[String]): this.type = put(key, value)

  /** 放入一个[[Metadata]]数组。 */
  def putMetadataArray(key: String, value: Array[Metadata]): this.type = put(key, value)

  /** 构建[[Metadata]]实例。 */
  def build(): Metadata = {
    new Metadata(map.toMap)
  }

  private def put(key: String, value: Any): this.type = {
    // 将键值对放入map中
    map.put(key, value)
    this
  }

  def remove(key: String): this.type = {
    // 移除指定的键值对
    map.remove(key)
    this
  }
}    

源码分析

  1. Metadata类的定义:Metadata类是一个不可变的、线程安全的类,用于存储键值对形式的元数据信息。它实现了SerializableProduct接口。entries字段是一个Map[String, Any]类型,用于存储键值对。

    case class Metadata(entries: Map[String, Any]) extends Serializable with Product {
      // ...
    }
    
  2. Metadata对象的创建:

    • 使用空的构造函数:可以使用空的构造函数创建一个空的Metadata对象。这将生成一个没有任何键值对的空元数据。
    • 使用apply方法:可以使用Metadata.apply方法创建一个Metadata对象,并传入一个键值对的序列。
    • 使用empty方法:可以使用Metadata.empty方法创建一个空的Metadata对象。
  3. Metadata的操作方法:

    • get方法:根据给定的键获取对应的值。如果键不存在,则返回None
    • contains方法:检查是否包含指定的键。
    • getString方法:获取指定键的字符串值。如果键不存在或者值的类型不是字符串类型,则返回None
    • getInt方法:获取指定键的整数值。如果键不存在或者值的类型不是整数类型,则返回None
    • getLong方法:获取指定键的长整型值。如果键不存在或者值的类型不是长整型,则返回None
    • getBoolean方法:获取指定键的布尔值。如果键不存在或者值的类型不是布尔类型,则返回None
    • getDouble方法:获取指定键的双精度浮点值。如果键不存在或者值的类型不是双精度浮点型,则返回None
    • getMetadata方法:**获取指定键的嵌套元数据。**如果键不存在或者值的类型不是元数据类型,则返回None
    • toString方法:将Metadata对象转换为字符串表示形式。
  4. MetadataBuilder类:MetadataBuilder类是一个用于构建Metadata对象的辅助类。它提供了一系列方法来设置键值对的元数据信息。可以通过putXXX方法来设置不同类型的键值对,最后使用build方法构建出Metadata对象。

    class MetadataBuilder {
      def putString(key: String, value: String): MetadataBuilder
      def putLong(key: String, value: Long): MetadataBuilder
      def putDouble(key: String, value: Double): MetadataBuilder
      def putBoolean(key: String, value: Boolean): MetadataBuilder
      def putMetadata(key: String, value: Metadata): MetadataBuilder
      def build(): Metadata
    }
    

以上是对Metadata类的详细源码分析。Metadata类提供了一种方便的方式来存储和访问字段的元数据信息,可以在Spark中用于描述DataFrame和StructType中字段的属性、标签等相关信息。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BigDataMLApplication

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值