第五章 数据保存与读取(一)

spark常见数据源
文件格式与文件系统

1、spark支持本地文件系统以及分布式分拣系统(NFS、HDFS、Amazon S3等)

2、spark支持多种不同文件格式,如文本文件、JSON、SequenceFile以及protocol buffer。

格式名称结构化备注
文本文件普通的文本文件,每行一条记录
JSON半结构化常见的基于文本的格式,半结构化;大多数库都也要求每行一条记录
CSV基于文本的格式,通常在电子表格中使用
SequenceFiles基于键值对数据的Hadoop文件格式
Protocol buffer一种快速的,节约空间的跨语言格式
对象文件用来将Spark作业中的数据存储下来以共享代码但 形式读取,改变类的时候它会失效,依赖于Java序列化

除了Spark中直接支持的输出机制,还可以对键数据(或成对数据)使用Hadoop的新旧文件API。如果有些格式忽略了键,spark会使用假的键代替(如null)

文本文件

spark将一个文本文件读取为一个RDD时,输入的每一行都会成为RDD的一个元素。也可以将多个完整的文本文件一次性读取为一个pair RDD,其中键是文件名,值时文件内容。

//在 Scala 中读取一个文本文件
//如果要控制分区数的话,可以指定 minPartitions
val input = sc.textFile("file:///home/holden/repos/spark/README.md")

如果读取的是多个文件,有两种方法:
1、继续使用textFile函数
2、如需知道数据各个部分来自哪个文件(比如将键房子文件名中的事件数据),有时候则希望同时处理整个文件。如果文件足够小,则使用SparkContext.wholeTextFiles()方法,该方法会返回一个pair RDD,键是文件名称。

//在Scala中求每个文件的平均值
val input = sc.wholeTextFiles("file://home/holden/salesFiles")
val result = input.mapValues{y =>
val nums = y.split(" ").map(x => x.toDouble)
nums.sum / nums.size.toDouble
}

保存文件

保存文件使用saveAsTextFile()方法,函数接收一个路径,将路径当做一个目录,将RDD中的内容并行输入到路径对应的文件中,该函数不能控制数据输出到哪个文件中,不过有些输出格式支持控制。

JSON

1、读取JSON

将数据作为文本文件读取,然后对JSON数据进行解析,该方法适用于每一行都是一个JSON数据。

如果所使用的语言构建JSON解析器开销较大,则可以使用mapPartitions()来重用解析器。

scala使用Jackson。

//在scala中读取JSON
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.DeserializationFeature
...
case class Person(name: String, lovesPandas: Boolean) // 必须是顶级类
...
// 将其解析为特定的case class。使用flatMap,通过在遇到问题时返回空列表(None)
// 来处理错误,而在没有问题时返回包含一个元素的列表(Some(_))
val result = input.flatMap(record => {
try {
Some(mapper.readValue(record, classOf[Person]))
} catch {
case e: Exception => None
}})

保存JSON

可以使用之前将字符串RDD转为解析好的JSON数据的库,将由结构化数据组成的RDD转化为字符串RDD,然后使用Spark的文本文件API写出去。

//在scala中保存为JSON
result.filter(p => P.lovesPandas).map(mapper.writeValueAsString(_))
.saveAsTextFile(outputFile)
逗号分割值与制表符分割值

逗号分隔值:CSV

制表符分割值:TSV

记录通常是一行一条,有时也可以跨行。与JSON字段不一样的是,这里的每条记录都没有相关联的字段名,只能得到对应的序号。常规做法是使用第一行中每列的值作为字段名。

CSV:scala使用opencsv库,如果所有字段都没有包含换行符,则可以使用textFile()读取并解析数据。

//在Scala中使用textFile()读取CSV
import Java.io.StringReader
import au.com.bytecode.opencsv.CSVReader
...
val input = sc.textFile(inputFile)
val result = input.map{ line =>
val reader = new CSVReader(new StringReader(line));
reader.readNext();
}

如果字段中有换行符,则需要完整读入每个文件,然后解析。如果每个文件都很大,读取和解析的过程可能会成为性能瓶颈。

在scala中完整读取CSV
case class Person(name: String, favoriteAnimal: String)
val input = sc.wholeTextFiles(inputFile)
val result = input.flatMap{ case (_, txt) =>
val reader = new CSVReader(new StringReader(txt));
reader.readAll().map(x => Person(x(0), x(1)))
}

保存CSV

保存CSV,可以通过重用输出编码器来加速。由于CSV中我们不会记录在每条记录中输出字段名,因此为了保持一致,需要创建一种映射关系,可以写一个函数,用于将各字段转为指定顺序的数组。

我们所使用的CSV库要输出到文件或者输出器,所以可以使用 StringWriter 或 StringIO来将结果放到 RDD 中。

//在scala中写CSV
pandaLovers.map(person => List(person.name, person.favoriteAnimal).toArray)
.mapPartitions{people =>
val stringWriter = new StringWriter();
val csvWriter = new CSVWriter(stringWriter);
csvWriter.writeAll(people.toList)
Iterator(stringWriter.toString)
}.saveAsTextFile(outFile)
SequenceFile

SequenceFile 是由没有相对关系结构的键值对文件组成的常用Hadoop格式。

SequenceFile文件有同步标记,Spark可以用它来定位到文件中的某个点,然后再与边界对齐。这可以使Spark使用多个节点并行读取SequenceFile文件。

因为Hadoop使用了一套自定义的序列化框架,因此SequenceFile是由实现Hadoop的Writable接口的元素组成。
表中列出了一些常见的数据类型以及其对应得到Writable类。
标准的经验法则是尝试在类名后面加上Writable这个词,检查它是否是org.apache.hadoop.io.Writable的子类。
如果无法找到所要写的对应的Writable类型,可以通过重载org.apeche.hadoop.io.Writable中的readfields和write类来首先自己的Writable类。

Hadoop的RecordReader会为每条记录重用同一个对象,因此直接调用RDD的cache会导致失败。我们可以使用一个简单的Map操作然后将结果缓存极客。另外,许多Hadoop Writable类没有实现java.io.Serializable接口,因此为了让他们能在RDD中使用,还是要用map()来转换他们

Scala类型Hadoop Writable类
IntIntWritable或VIntWritable(变长)
LongLongWritable或VLongWritable(变长)
FloatFloatWritable
DoubleDoubleWritable
BooleanBooleanWritable
Array[Byte]BytesWriable
StringText
Array[T]ArrayWriable
List[T]ArrayWritable
Map[A,B]MapWritable
//scala中读取SequenceFile
val data = sc.sequenceFile(inFile, classOf[Text], classOf[IntWritable]).
map{case (x, y) => (x.toString, y.get())}

2、保存SequenceFile

SequenceFile存储的是键值对,所以需要创建一个由可以写出到SequenceFile的类型构成的pairRDD。
如果写出的类型是Scala的原生类型,可以直接调用saveSequenceFile(path)保存PariRDD。
如果键和值不能自动转为Writable类型,或者想使用变长类型,就可以对数据进行映射操作,在保存之前进行类型转换操作。

//在Scala中保存SequenceFile
val data = sc.parallelize(List(("Panda", 3), ("Kay", 6), ("Snail", 2)))
data.saveAsSequenceFile(outputFile)

3、对象文件

对象文件看起来就像是对SequenceFile的简单封装,它允许存储只包含值的RDD。与SequenceFile不一样的是,对象文件是使用Java序列化写出的。

保存:saveAsObjectFile文件

读取:SparkContext中的objectFile()函数接受一个路径, 返回对应的RDD

对象文件在Python中无法使用,不过Python中的RDD和SparkContext支持saveAsPickleFile()和pickleFile()方法作为代替。这使用了Python的pickle序列化库。不过,对象文件的注意事项同样会用于Pickle文件;pickle库可能很慢,并且修改类定以后,已经产生的数据文件可能无法再读取出来。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值