Spark 写hdfs自定义文件名

spark data写入机制:

Spark 写hdfs自定义文件名_第1张图片
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")

你可能感兴趣的:(Spark,Hadoop,spark,hadoop)