更改DataFrame.write()

时间:2016-03-19 21:46:43

标签: java apache-spark mapreduce apache-spark-sql

通过Spark SQL DataFrame.write()方法生成的输出文件以" part"开头。 basename前缀。 e.g。

DataFrame sample_07 = hiveContext.table("sample_07");
sample_07.write().parquet("sample_07_parquet");

结果:

hdfs dfs -ls sample_07_parquet/                                                                                                                                                             
Found 4 items
-rw-r--r--   1 rob rob          0 2016-03-19 16:40 sample_07_parquet/_SUCCESS
-rw-r--r--   1 rob rob        491 2016-03-19 16:40 sample_07_parquet/_common_metadata
-rw-r--r--   1 rob rob       1025 2016-03-19 16:40 sample_07_parquet/_metadata
-rw-r--r--   1 rob rob      17194 2016-03-19 16:40 sample_07_parquet/part-r-00000-cefb2ac6-9f44-4ce4-93d9-8e7de3f2cb92.gz.parquet

我想更改使用Spark SQL DataFrame.write()创建文件时使用的输出文件名前缀。我尝试设置" mapreduce.output.basename" Spark上下文的hadoop配置的属性。 e.g。

public class MyJavaSparkSQL {

  public static void main(String[] args) throws Exception {
    SparkConf sparkConf = new SparkConf().setAppName("MyJavaSparkSQL");
    JavaSparkContext ctx = new JavaSparkContext(sparkConf);
    ctx.hadoopConfiguration().set("mapreduce.output.basename", "myprefix");
    HiveContext hiveContext = new org.apache.spark.sql.hive.HiveContext(ctx.sc());
    DataFrame sample_07 = hiveContext.table("sample_07");
    sample_07.write().parquet("sample_07_parquet");
    ctx.stop();
  }

这没有改变生成文件的输出文件名前缀。

有没有办法在使用DataFrame.write()方法时覆盖输出文件名前缀?

3 个答案:

答案 0 :(得分:7)

使用任何标准输出格式(如Parquet)时,无法更改“part”前缀。请参阅ParquetRelation source code中的此片段:

private val recordWriter: RecordWriter[Void, InternalRow] = {
  val outputFormat = {
    new ParquetOutputFormat[InternalRow]() {
      // ...
      override def getDefaultWorkFile(context: TaskAttemptContext, extension: String): Path = {
        // ..
        //  prefix is hard-coded here:
        new Path(path, f"part-r-$split%05d-$uniqueWriteJobId$bucketString$extension")
    }
  }
}

如果你真的必须控制零件文件名,你可能必须实现一个自定义FileOutputFormat并使用一个接受FileOutputFormat类的Spark的保存方法(例如saveAsHadoopFile)。

答案 1 :(得分:0)

假设输出文件夹中仅包含一个csv文件,我们可以使用以下代码对其进行语法化(动态)重命名。在下面的代码(最后一行)中,使用csv类型从输出目录中获取所有文件,并将其重命名为所需的文件名。

import org.apache.hadoop.fs.{FileSystem, Path}
import org.apache.hadoop.conf.Configuration
val outputfolder_Path = "s3://<s3_AccessKey>:<s3_Securitykey>@<external_bucket>/<path>"     
val fs = FileSystem.get(new java.net.URI(outputfolder_Path), new Configuration())   
fs.globStatus(new Path(outputfolder_Path + "/*.*")).filter(_.getPath.toString.split("/").last.split("\\.").last == "csv").foreach{l=>{ fs.rename(new Path(l.getPath.toString), new Path(outputfolder_Path + "/DesiredFilename.csv")) }}

答案 2 :(得分:0)

同意@Tzach Zohar..


将数据帧保存到 HDFS 或 S3 后,您可以使用以下重命名...

下面的 scala 示例已经准备就绪 :-) 意味着您可以直接在您的代码或 util 中使用 写入 HDFS 或 S3 后,您可以使用以下定义重命名文件..

#简要

1) 使用 globstatus 获取文件夹下的所有文件。
2)循环并使用前缀或后缀重命名文件。
注意:Apache Commons 已经在 hadoop 集群中可用,所以不需要任何进一步的依赖。

/**
   * prefixHdfsFiles
   * @param outputfolder_Path
   * @param prefix
   */
  def prefixHdfsFiles(outputfolder_Path: String, prefix: String) = {
    import org.apache.hadoop.fs.{_}
    import org.apache.hadoop.conf.Configuration
    import org.apache.commons.io.FilenameUtils._
    import java.io.File
    import java.net.URI

    val fs = FileSystem.get(new URI(outputfolder_Path), new Configuration())
    fs.globStatus(
      new Path(outputfolder_Path + "/*.*")).foreach { l: FileStatus => {
      val newhdfsfileName = new Path(getFullPathNoEndSeparator(l.getPath.toString) + File.separatorChar + prefix + getName(l.getPath.toString))
     // fs.rename(new Path(l.getPath.toString),newhdfsfileName )
      val change = s"""
        |original ${ new Path(l.getPath.toString) } --> new $newhdfsfileName
        |""".stripMargin
      println( change)
    }
    }
  }

来电者例如:


val outputfolder_Path = "/a/b/c/d/e/f/"
    prefixHdfsFiles(outputfolder_Path, "myprefix_")