spark data写入机制:
dataframe保存到指定路径,一般都是一个文件夹,具体保存文件是文件夹内部的 part-00000*文件。
1.hdfs-api改名
/**
* 保存DataFrame到指定名称文件
*
* @param DF 希望保存的DataFrame
* @param fullPath 希望保存的最终文件路径,s"/data/test/part-0000-*",不包含后缀
* @param suffix 文件类型txt
* @return 最终保存文件名称 /data/test.csv
*/
def saveDF2HDFSAsTxt(df: DataFrame, fullPath: String, suffix: String): String = {
val hdfs: FileSystem = org.apache.hadoop.fs.FileSystem.get(new org.apache.hadoop.conf.Configuration())
//0.df先保存
df.coalesce(1)
.write
.format("csv")
.mode("overwrite")
.option("header", "true")
.option("mapreduce.fileoutputcommitter.marksuccessfuljobs", "false")
.save(fullPath)
//1.先读hdfs 拿到该目录下的文件名字
val statuses: Array[FileStatus] = hdfs.listStatus(new Path(s"$fullPath"))
val filePath: Path = FileUtil.stat2Paths(statuses)(0)
//2.替换名称
hdfs.rename(filePath, new Path(s"$fullPath.$suffix"))
s"$fullPath.$suffix"
}
2.自定义FileoutputFormat
这种有个缺点:保存为csv时第一行会是空列
- MyFileOutPutFormat
import org.apache.hadoop.fs.{FileSystem, Path}
import org.apache.hadoop.mapred.{FileOutputFormat, JobConf}
import org.apache.hadoop.mapred.lib.MultipleTextOutputFormat
class MyFileOutPutFormat extends MultipleTextOutputFormat[String,String]{
//重写generateFileNameForKeyValue方法,该方法是负责自定义生成文件的文件名
override def generateFileNameForKeyValue(key: String, value: String, name: String): String = {
key+".csv"
}
/**
*因为saveAsHadoopFile是以key,value的形式保存文件,写入文件之后的内容也是,按照key value的形式写入,k,v之间用空格隔开,
* 这里我只需要写入value的值,不需要将key的值写入到文件中个,所以我需要重写
*该方法,让输入到文件中的key为空即可,当然也可以进行领过的变通,也可以重写generateActuralValue(key:Any,value:Any),根据自己的需求来实现
*/
override def generateActualKey(key: String, value: String): String = {
null
}
// 处理value
override def generateActualValue(key: String, value: String): String = {
value
}
/***
* 该方法使用来检查我们输出的文件目录是否存在,源码中,是这样判断的,如果写入的父目录已经存在的话,则抛出异常
* 在这里我们冲写这个方法,修改文件目录的判断方式,如果传入的文件写入目录已存在的话,直接将其设置为输出目录即可,
* 不会抛出异常
*/
override def checkOutputSpecs(ignored: FileSystem, job: JobConf): Unit ={
val outdir: Path = FileOutputFormat.getOutputPath(job)
if (outdir!=null){
注意下面的这句,如果说你要是写入文件的路径是hdfs的话,下面的这句不要写
// 这句作用是标准化文件输出目录,他们是标准化本地路径,写入本地的话,可以加上,本地路径记得要用file:///开头
// val outdir: Path = ignored.makeQualified(outdir)
FileOutputFormat.setOutputPath(job,outdir)
}
}
}
- saveCsvFile
savePath = s"/tmp/${LocalDate.now}/$fileName/"
df.show(false)
// 判断该路径下是否有数据 有 -- 删除
val fs: FileSystem = FileSystem.get(new Configuration())
if (fs.exists(new Path(savePath))) {
fs.delete(new Path(savePath), true)
System.out.println(s"$savePath 下有文件,现在已删除")
}
//处理df的header
val schemaRdd: RDD[String] = session.sparkContext.parallelize(Array(df.schema.fieldNames.mkString("\t")))
log.info("schema union data...")
val rdd3: RDD[String] = schemaRdd
.union(
df
.limit(10) //测试
.rdd
.mapPartitions(iter => {
iter.map(row => row.mkString("\t")) //处理数据
})
)
rdd3.foreach(println)
log.info("开始写hdfs...")
rdd3.mapPartitions(iter => {
iter.map(row => (fileName, "," + "\"" + row.replaceAll("\t", "\",\"") + "\"")) //字段内带逗号,需加引号处理下
})
.partitionBy(new HashPartitioner(1))
.saveAsHadoopFile(savePath, classOf[String], classOf[String], classOf[MyFileOutPutFormat]) // 自定义文件名称 并输出到指定目录
log.info("saveAsHadoopFile success")