Spark指定保存到HDFS的具体文件名称

一、实现功能

dataframe保存到指定路径,一般都是一个文件夹,具体保存文件是文件夹内部的 part-00000*文件。对于需要指定最终保存文件的场景,本身api无法实现。本文提供保存到指定文件夹内,并且指定最终文件名称的两种方法。

二、方法1:直接使用hdfs的api实现修改文件名称

1.实现思路

首先,DataFrame先减少分区到1个,再转换为RDD,然后写入hdfs,因为DataFrame直接保存的话,会有随机后缀part-00000*,无法确定那个文件。而RDD保存,如果只有一个分区的话,最终文件肯定是part-00000。

然后,使用hdfs的api,将part-00000文件改为需要名称。

2.具体实现代码

  /**
   * 保存DataFrame到指定名称文件
   * @param DF 希望保存的DataFrame
   * @param fullPath 希望保存的最终文件路径,s"/data/test_$id",不包含后缀
   * @param suffix 文件类型txt
   * @return 最终保存文件名称 /data/test_$id.txt
   */
  def saveDF2HDFSAsTxt(DF: DataFrame, fullPath: String, suffix: String): String = {
    val hdfs: FileSystem = org.apache.hadoop.fs.FileSystem.get(new org.apache.hadoop.conf.Configuration())
    //首先,先删除原有的文件目录
    if (hdfs.exists(new Path(fullPath))) {
      hdfs.delete(new Path(fullPath), true)
    }
    if (!hdfs.exists(new Path(fullPath))) {
      //1.先转换rdd,再保存
      DF.coalesce(1).rdd.saveAsTextFile(fullPath)
      //2.替换名称
      hdfs.rename(new Path(s"$fullPath/part-00000"), new Path(s"$fullPath.$suffix"))
      //3.删除临时目录
      hdfs.delete(new Path(fullPath), true)
    }
    val finalhdfspath=s"$fullPath.$suffix"
    finalhdfspath
  }

三、方法2:修改保存 outputFormat 类

1.实现思路

首先,需要自定一个一个类重写outputFormat类中的generateFileNameForKeyValue等相关方法。

然后,将RDD转换为PairRDD,进而用saveAsHadoopFile的方式进行保存文件。因为,如果是使用saveAsTextFile的方式的话,因为只有能传入一个参数;而使用saveAsHadoopFile方式是针对<k,v>对的RDD进行保存,key和value以空格分开,相同key保存在同一个文件中。

最后,修改对应方法generateActualKey,不保存key从而保存最终数据。

2.实现代码

2.1 重写 FileoutputFormat

import org.apache.hadoop.fs.{FileSystem, Path}
import org.apache.hadoop.mapred.{FileOutputFormat, JobConf}
import org.apache.hadoop.mapred.lib.MultipleTextOutputFormat

class CustomOutputFormat extends MultipleTextOutputFormat[Any, Any] {

  /**
   *   重写generateFileNameForKeyValue方法,该方法是负责自定义生成文件的文件名
   */
  override def generateFileNameForKeyValue(key: Any, value: Any, name: String): String = {
    //这里的key和value指的就是要写入文件的rdd对,再此,我定义文件名以key.txt来命名,当然也可以根据其他的需求来进行生成文件名
    val fileName = key.asInstanceOf[String] + ".txt"
    fileName
  }

  /**
   *因为saveAsHadoopFile是以key,value的形式保存文件,写入文件之后的内容也是,按照key value的形式写入,
   * k,v之间用空格隔开,这里我只需要写入value的值,不需要将key的值写入到文件中个,所以需要重写该方法。
   * 让输入到文件中的key为空即可,当然也可以进行领过的变通,也可以重写generateActuralValue(key:Any,value:Any),根据自己的需求来实现
   */
  override def generateActualKey(key: Any, value: Any): String = {
    null
  }

  //对生成的value进行转换为字符串,当然源码中默认也是直接返回value值,如果对value没有特殊处理的话,不需要重写该方法
//  override def generateAcutalValue(key: Any, value: Any): String = {
//    return value.asInstanceOf[String]
//  }
  /**
   * 该方法使用来检查我们输出的文件目录是否存在,源码中,是这样判断的,如果写入的父目录已经存在的话,则抛出异常
   * 在这里我们重写这个方法,修改文件目录的判断方式,如果传入的文件写入目录已存在的话,直接将其设置为输出目录即可,
   * 不会抛出异常
   */
  override def checkOutputSpecs(ignored: FileSystem, job: JobConf): Unit = {
    var outDir: Path = FileOutputFormat.getOutputPath(job)
    if (outDir != null) {
      //注意下面的这两句,如果说你要是写入文件的路径是hdfs的话,下面的两句不要写,或是注释掉,
      // 它俩的作用是标准化文件输出目录,根据我的理解是,他们是标准化本地路径,写入本地的话,可以加上,本地路径记得要用file:///开头,比如file:///E:/a.txt
      //val fs: FileSystem = ignored
      //outDir = fs.makeQualified(outDir)
      FileOutputFormat.setOutputPath(job, outDir)
    }
  }
}

2.2 代码中调用

package com.ray.test

import com.alibaba.fastjson.JSONArray
import org.apache.hadoop.fs.{FileSystem, Path}
import org.apache.spark.sql.DataFrame

object onlyTest {

  def main(args: Array[String]) {
      
      ...
      
    fileRDD.map(x=>("ids",x)).partitionBy(new HashPartitioner(1))
      .saveAsHadoopFile("/data/tag",classOf[String],classOf[String],classOf[CustomOutputFormat])
    
      ...
  }

}

2.3 验证结果

hdfs dfs -cat /data/tag/ids.txt | head -n 30
{"Ids":"ef6er3","userName":"zhangsan"}
{"userId":"dfse","userName":"lisi"}
{"userId":"fetsd3","userName":"wangwu"}

..

四、参考

1.关于spark写入文件至文件系统并制定文件名之自定义outputFormat

2.spark 保存文件到hdfs,自己指定文件名称

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值