这些天突然发觉基础的东西真的还蛮重要的。尤其当理解到位了,能随心所用的时候,真的能优化代码质量,提高工作效率。
我以前都不喜欢整理和记录这些很细节琐碎的事情,总是想从宏观的角度去把握和理解事情。现在也开始关注些细节。整体和细节,两手抓才能行得稳,走得长远吧。
概念理解
Spark中的DataFrame和Datasets是具有定义好的行、列的(分布式的)数据表。(可以具象理解成EXCEL表格)
Spark中DataFrame和Datasets是不可变的、lazily懒惰执行的,只有当执行一个action操作时,才会执行并返回结果。
表、视图 和DataFrame基本上是一样的。只不过在表和视图上,我们用SQL代码写操作命令,在DataFrame上,我们用DataFrame的操作代码。
在结构化API中,DataFrame是非类型化(untyped)的,Datasets是类型化(typed)的。说DataFrame非类型化,指的是spark只在运行(runtime)的时候检查数据的类型是否与指定的schema一致,而Datasets在编译(compile)的时候就检查数据类型是否符合规范。
DataFrame是Row类型的简单版的Datasets。Row类型是Spark为计算而优化的内存格式的内部表示形式,有助于高效计算。JVM类型会导致很高的垃圾回收、对象实例化成本,因此Spark不使用JVM类型,而是在自己内部的sprak数据类型上运行。
Schemas
schema定义了列名和数据类型。Schema是Spark的StructType类型,由一些域StructFields组成,域中明确了列名、列类型、以及一个布尔类型的参数来表示该列是否可以有缺失值或null值,最后也可以可选择的明确该列关联的元数据。(机器学习库中元数据是一种存储列信息的方式,平常很少用到。)
有两种方式定义:手动定义、或从数据源中读入。
使用手动定义Schema还是从数据中读入取决于现实使用场景。多数情况下读入就可以了,但有时候在解析CSV/JSON文本获取Schema时稍微有点慢,另外,也可能会有准确性问题,比如类型推断时,可能把Long类型识别成Int类型。当使用Spark做线上生产环境的ETL时,建议使用人工手动定义的方法。
方式一:手动定义schema
--in scala
//in scala
import org.apache.spark.sql.types._
import org.apache.spark.sql.types.Metadata
//手动定义Schema
val schema = StructType(Seq(
StructField("columnA", IntegerType, nullable = false,Meatadata.fromJson("{\"hello\":\"world\"}")),
StructField("columnB", StringType, nullable = true),
StructField("columnC", DateType, nullable = true)
))
//使用手动定义的Schema作为创建的DF的Schema
val dfJson = spark.read.format("json").schema(myManualSchema).load("/data/test.json")
//打印Schema
dfJson.printSchema()
--in python
# in Python
from pyspark.sql.types import StructField, StructType, StringType, LongType
myManualSchema = StructType([
StructField("sku_id", StringType(), True),
StructField("itm_sku_nm", StringType(), True),
StructField("attr_name", StringType(), True),
StructField("attr_value", StringType(), True)
])
cate_df=spark.read.format("csv").schema(myManualSchema).load("hdfs://user/ad/dev/sku_attr_data")
cate_df.cache()
cate_df.createOrReplaceTempView("sku_title")
spark.sql('''select * from sku_title limit 100''').show()
方式二:从数据源中读入
//从文件header中推断schema
val dfCsvSchema = spark.read.format("csv").option("header",true).load("/data/test.csv").schema
//从文件读入数据到DF,输出其推断的Schema
val dfJsonSchema = spark.read.format("json").load("/data/test.json").schema
常见使用方式示例:
//读入数据,推断Schema,创建视图
val df = spark.read.format("csv")
.option("header", "true")
.option("inferSchema", "true")
.load("/data/day/2010-12-01.csv")
df.printSchema()
df.createOrReplaceTempView("dfTable")
Columns
Columns列代表了一个数据类型,比如整数型、String型,或者Array、map类型,或者null值。Spark会跟踪所有列的类型信息,并且提供列变换的方法。你可以选择、删除、对列进行计算等操作,这些操作被表示成表达式expressions。对Spark,列其实是基于表达式对每条记录(record)的值进行计算的逻辑构建。
要想一列有值,得先有row;要有ro w,必须要有一个DataFrame。不能脱离DataFrame的环境对列进行操作,必须在一个DataFrame中使用spark变换来修改列的内容。需要注意的是列在与catalog中的列名进行比较之前是不解析的(not resolved),列和表的解析是在analyzer阶段。这就是说,你的代码可能发现不了列名不存在的bug。(如果使用IDEA并安装了插件,是可以发现列名引用错误的,而且compile时也会爆出来。)
- 引用列的方法-col or colmun 函数 或语法糖 $ or '
import org.apache.spark.sql.functions.{col,column}
//列引用的方式
df.col("count")
df.column("count")
df.$"count"
df.'count
- 获取DataFrame的列名
//获取列名
val colnames = spark.read.format("json").load("/data/test.json").columns
Rows
DataFrame的每一条记录都是Row类型。Spark用列表达式操作Row对象来生成计算数据。创建Row类型数据的方式有:从SQL手工创建、从RDD创建、读取数据源。
//使用range函数创建Row类型的数据集
spark.range(4).toDF().collect()
//从DF中查看数据返回的是一个Row对象类型
df.first()
- 手动创建Rows对象
Row对象是没有Schema的,只有DF有,所以手动创建Row对象时,要注意数据的位置与DF中Schema定义的顺序一致。
import org.apache.spark.sql.Row
val myRow = Row("Hello",null,9,false)
- 获取Row对象中的数据
根据位置获取想要的数据就可以。
myRow(0) //Any 类型
myRow(0).asInstanceOf[String] //转成String类型
myRow.getString(0) //String类型
myRow.getInt(9) //Int类型
Spark Types
Spark有自己内部的类型表示。当我们用代码语言(scala、Python、Java、R)来指定数据类型时,它实际上会通过查表,将我们使用语言的数据类型转成spark内部的数据类型。不同语言(python\scala)的数据类型 与 spark数据类型的对应表在后面给出。
首先,来看下数据类型的实例化
//实例化数据类型
import org.apache.spark.sql.types._
val b = ByteType
scala语言的数据类型映射参考:
Data type | Value type in Scala | API to access or create a data type |
ByteType | Byte | ByteType |
ShortType | Short | ShortType |
IntegerType | Int | IntegerType |
LongType | Long | LongType |
FloatType | Float | FloatType |
DoubleType | Double | DoubleType |
DecimalType | java.math.BigDecimal | DecimalType |
StringType | String | StringType |
BinaryType | Array[Byte] | BinaryType |
BooleanType | Boolean | BooleanType |
TimestampType | java.sql.Timestamp | TimestampType |
DateType | java.sql.Date | DateType |
ArrayType | scala.collection.Seq | ArrayType(elementType, [containsNull]). Note: The default value of containsNull is true. |
MapType | scala.collection.Map | MapType(keyType, valueType, [valueContainsNull]). Note: The default value of valueContainsNull is true. |
StructType | org.apache.spark.sql.Row | StructType(fields). Note: fields is an Array of StructFields. Also, fields with the same name are not allowed. |
StructField | The value type in Scala of the data type of this field (for example, Int for a StructField with the data type IntegerType) | StructField(name, dataType, [nullable]). Note: The default value of nullable is true. |