Spark sql schema StructField中metadata源码分析
文章目录
- Spark sql schema StructField中metadata源码分析
- 原理
- 用法
- 示例
- 中文源码
- class Metadata
- object Metadata
- MetadataBuilder
- 源码分析
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
}
}
源码分析
Metadata
类的定义:Metadata
类是一个不可变的、线程安全的类,用于存储键值对形式的元数据信息。它实现了Serializable
和Product
接口。entries
字段是一个Map[String, Any]
类型,用于存储键值对。
case class Metadata(entries: Map[String, Any]) extends Serializable with Product {
// ...
}
Metadata
对象的创建:
- 使用空的构造函数:可以使用空的构造函数创建一个空的
Metadata
对象。这将生成一个没有任何键值对的空元数据。 - 使用
apply
方法:可以使用Metadata.apply
方法创建一个Metadata
对象,并传入一个键值对的序列。 - 使用
empty
方法:可以使用Metadata.empty
方法创建一个空的Metadata
对象。
Metadata
的操作方法:
-
get
方法:根据给定的键获取对应的值。如果键不存在,则返回None
。 -
contains
方法:检查是否包含指定的键。 -
getString
方法:获取指定键的字符串值。如果键不存在或者值的类型不是字符串类型,则返回None
。 -
getInt
方法:获取指定键的整数值。如果键不存在或者值的类型不是整数类型,则返回None
。 -
getLong
方法:获取指定键的长整型值。如果键不存在或者值的类型不是长整型,则返回None
。 -
getBoolean
方法:获取指定键的布尔值。如果键不存在或者值的类型不是布尔类型,则返回None
。 -
getDouble
方法:获取指定键的双精度浮点值。如果键不存在或者值的类型不是双精度浮点型,则返回None
。 -
getMetadata
方法:**获取指定键的嵌套元数据。**如果键不存在或者值的类型不是元数据类型,则返回None
。 -
toString
方法:将Metadata
对象转换为字符串表示形式。
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中字段的属性、标签等相关信息。