【问题标题】:Write to multiple outputs by key Spark - one Spark job通过键 Spark 写入多个输出 - 一项 Spark 作业
【发布时间】:2014-07-22 14:25:12
【问题描述】:

如何在单个作业中使用 Spark 写入依赖于密钥的多个输出。

相关:Write to multiple outputs by key Scalding Hadoop, one MapReduce Job

例如

sc.makeRDD(Seq((1, "a"), (1, "b"), (2, "c")))
.writeAsMultiple(prefix, compressionCodecOption)

将确保cat prefix/1

a
b

cat prefix/2 将是

c

编辑:我最近添加了一个新答案,其中包括完整的导入、pimp 和压缩编解码器,请参阅https://stackoverflow.com/a/46118044/1586965,除了早期的答案之外,这可能会有所帮助。

【问题讨论】:

  • 您能否添加一个示例来说明您的目标?也许是输入集合以及您期望的过程/输出?
  • 不,“完成”正如完成 maasg 的请求@fengyun。我们有一个用例,特别是目前我们有一个使用 TemplatedTsv 的 Scalding 作业,我们想用 Spark 作业替换它。但是重构一直在我们的积压中......当我最终写出一个完整的基于 hdfs 的解决方案时,我一定会在这里发布它。
  • this example 有帮助吗?我正在尝试自己弄清楚如何做到这一点。
  • 仅供参考:我已打开 SPARK-3533 请求将更直接的方法添加到 Spark。

标签: scala hadoop output hdfs apache-spark


【解决方案1】:

如果您使用 Spark 1.4+,感谢DataFrame API,这将变得非常容易。 (DataFrames 是在 Spark 1.3 中引入的,但我们需要的 partitionBy()introduced in 1.4。)

如果您从 RDD 开始,您首先需要将其转换为 DataFrame:

val people_rdd = sc.parallelize(Seq((1, "alice"), (1, "bob"), (2, "charlie")))
val people_df = people_rdd.toDF("number", "name")

在 Python 中,同样的代码是:

people_rdd = sc.parallelize([(1, "alice"), (1, "bob"), (2, "charlie")])
people_df = people_rdd.toDF(["number", "name"])

一旦你有了一个 DataFrame,根据一个特定的键写入多个输出就很简单了。更重要的是——这就是 DataFrame API 的美妙之处——代码在 Python、Scala、Java 和 R 中几乎相同:

people_df.write.partitionBy("number").text("people")

如果需要,您可以轻松使用其他输出格式:

people_df.write.partitionBy("number").json("people-json")
people_df.write.partitionBy("number").parquet("people-parquet")

在每个示例中,Spark 将为我们在 DataFrame 上分区的每个键创建一个子目录:

people/
  _SUCCESS
  number=1/
    part-abcd
    part-efgh
  number=2/
    part-abcd
    part-efgh

【讨论】:

  • 你能在 Scala 中添加等效的Datasets 代码吗?我会接受作为最佳答案。是的,有些人不关心类型,喜欢每隔几分钟运行一次他们的整个应用程序,看看他们是否有任何错误,但我们中的一些人喜欢在我们输入它的那一刻抓住像“nubmer”这样的错字:) 说真的,很好回答。
  • @samthebest - 仅供参考,我回滚了您的编辑,因为它存在一些问题:它不符合我的写作风格;我对Datasets不太了解,所以关于Dataset[SomeCaseClass]的注释更适合作为评论;最后,Python 没有makeRDD() 方法。
  • 请注意,如果您有Dataset[SomeCaseClass],那么您只需调用.toDF(),列标签将匹配SomeCaseClasses 字段。这提供了更多的类型安全性。
  • 有没有办法强制这个方法每个分区只写一个文件/部分?
  • @moustachio - 好问题。我认为您可以通过将 DataFrame 合并到 partitionBy() 之前的一个分区来强制执行此操作。例如:people_df.coalesce(1).write.partitionBy("number").text("people") 这可能会限制 Spark 在写出数据时的并行性,但这取决于您的数据和集群配置。
【解决方案2】:

我会这样做,这是可扩展的

import org.apache.hadoop.io.NullWritable

import org.apache.spark._
import org.apache.spark.SparkContext._

import org.apache.hadoop.mapred.lib.MultipleTextOutputFormat

class RDDMultipleTextOutputFormat extends MultipleTextOutputFormat[Any, Any] {
  override def generateActualKey(key: Any, value: Any): Any = 
    NullWritable.get()

  override def generateFileNameForKeyValue(key: Any, value: Any, name: String): String = 
    key.asInstanceOf[String]
}

object Split {
  def main(args: Array[String]) {
    val conf = new SparkConf().setAppName("Split" + args(1))
    val sc = new SparkContext(conf)
    sc.textFile("input/path")
    .map(a => (k, v)) // Your own implementation
    .partitionBy(new HashPartitioner(num))
    .saveAsHadoopFile("output/path", classOf[String], classOf[String],
      classOf[RDDMultipleTextOutputFormat])
    spark.stop()
  }
}

刚刚看到上面类似的答案,但实际上我们不需要自定义分区。 MultipleTextOutputFormat 将为每个键创建文件。具有相同键的多条记录落入同一个分区是可以的。

new HashPartitioner(num),这里的num是你想要的分区号。如果您有大量不同的键,您可以将 number 设置为 big。在这种情况下,每个分区不会打开太多的 hdfs 文件处理程序。

【讨论】:

  • 请您添加所有必要的导入语句吗?我没有对此进行测试,但接受了似乎是我想要的答案。 partitionBy(new Hashpartitioner(num)) 有什么意义??这不是和repartition(num)一样吗??
  • 不一样。 hash分区会保证所有具有相同key的记录去到同一个分区。我记得 repartition 没有这个功能。
  • 非常感谢这个非常好的解决方案。我只是想知道以下几点:我应该如何修改您的代码以便让每个文件的输出按值 v 排序?
  • 我一直在寻找编写多个 parquet 输出的方法,而这种解决方案看起来很有希望(仅直接继承 MultipleOutputFormat,不使用 MultipleTextOutputFormat)。不幸的是 MutlipleOutputFormat 只存在于旧 API MR1/mapred 中,而 AvroParquetOutputFormat 和 ParquetOutputFormat(支持 parquet)是针对新 API MR2/mapreduce 编写的,所以似乎相同的路径没有打开......
  • 看起来很棒!有python等价物吗?
【解决方案3】:

如果给定键可能有多个值,我认为可扩展的解决方案是为每个分区的每个键写出一个文件。不幸的是,Spark 中没有对此的内置支持,但我们可以做出一些改进。

sc.makeRDD(Seq((1, "a"), (1, "b"), (2, "c")))
  .mapPartitionsWithIndex { (p, it) =>
    val outputs = new MultiWriter(p.toString)
    for ((k, v) <- it) {
      outputs.write(k.toString, v)
    }
    outputs.close
    Nil.iterator
  }
  .foreach((x: Nothing) => ()) // To trigger the job.

// This one is Local, but you could write one for HDFS
class MultiWriter(suffix: String) {
  private val writers = collection.mutable.Map[String, java.io.PrintWriter]()
  def write(key: String, value: Any) = {
    if (!writers.contains(key)) {
      val f = new java.io.File("output/" + key + "/" + suffix)
      f.getParentFile.mkdirs
      writers(key) = new java.io.PrintWriter(f)
    }
    writers(key).println(value)
  }
  def close = writers.values.foreach(_.close)
}

(将 PrintWriter 替换为您选择的分布式文件系统操作。)

这会在 RDD 上进行一次传递,并且不会执行随机播放。它为每个键提供一个目录,每个目录中有许多文件。

【讨论】:

  • 谢谢。如果我们在哪里使用 HDFS 而不是本地文件系统,因为我们基本上将自己手动实现 shuffle 部分,对吗?另外,当多个分区包含具有相同键的对时会发生什么?两个任务都可能尝试写入同一个文件,因此我们需要某种同步的文件管理系统来跟踪创建 part-XXXXX。鉴于我确定存在使用 MultipleOutputFormat 的解决方案,恐怕这个解决方案会让人觉得很脏。
  • 你说得对,它是一种实施洗牌。但我认为没有瓶颈。没有一个节点可以接收所有带有键的记录。同一个key来自多个partition没有问题,也不需要同步。文件名为output/&lt;key&gt;/&lt;partition&gt;。所以每个分区写入不同的文件。 (分区索引在示例中为suffix。)
  • MultipleOutputFormat 听起来非常适合这项工作,并且会按照相同的想法工作。我只是从未使用过它。我认为您只需重写我的MultiWriter 以使用MultipleOutputFormat 而不是滚动其自己的密钥-> 文件映射。但mapPartitionsWithIndex 位将基本保持不变。
  • 抱歉,我误解了您的解决方案(我略读了一下)。感谢您的澄清。是的,我认为通过一些玩弄并用 HDFS 替换编写器代码,这将起作用(也没有瓶颈)。谢谢你的回答。
  • 我担心当我们使用 mapPartitionsWithIndex 并手动写入 HDFS 时,该特定分区不一定会输出到该分区的所需位置。因此额外的洗牌是不必要的,可以避免。
【解决方案4】:

这包括请求的编解码器、必要的导入和请求的 pimp。

import org.apache.spark.rdd.RDD
import org.apache.spark.sql.SQLContext

// TODO Need a macro to generate for each Tuple length, or perhaps can use shapeless
implicit class PimpedRDD[T1, T2](rdd: RDD[(T1, T2)]) {
  def writeAsMultiple(prefix: String, codec: String,
                      keyName: String = "key")
                     (implicit sqlContext: SQLContext): Unit = {
    import sqlContext.implicits._

    rdd.toDF(keyName, "_2").write.partitionBy(keyName)
    .format("text").option("codec", codec).save(prefix)
  }
}

val myRdd = sc.makeRDD(Seq((1, "a"), (1, "b"), (2, "c")))
myRdd.writeAsMultiple("prefix", "org.apache.hadoop.io.compress.GzipCodec")

与 OP 的一个细微差别是它将 &lt;keyName&gt;= 前缀到目录名称。例如

myRdd.writeAsMultiple("prefix", "org.apache.hadoop.io.compress.GzipCodec")

愿意:

prefix/key=1/part-00000
prefix/key=2/part-00000

其中prefix/my_number=1/part-00000 将包含行ab,而prefix/my_number=2/part-00000 将包含行c

myRdd.writeAsMultiple("prefix", "org.apache.hadoop.io.compress.GzipCodec", "foo")

愿意:

prefix/foo=1/part-00000
prefix/foo=2/part-00000

应该清楚如何编辑parquet

最后是Dataset 的示例,这可能比使用元组更好。

implicit class PimpedDataset[T](dataset: Dataset[T]) {
  def writeAsMultiple(prefix: String, codec: String, field: String): Unit = {
    dataset.write.partitionBy(field)
    .format("text").option("codec", codec).save(prefix)
  }
}

【讨论】:

  • 不确定它没有 +100 的赞成票,实际上赞成票为零。很有帮助,谢谢!
  • @Aliostad,看看日期,这是一年半后发布的。此外,在 SO 发布对您自己的问题的答案(在它已经有一个或多个有效问题之后)并接受它是 习惯的(有时被认为是粗鲁的)。有时情况需要多个答案,但随后您通常会接受原始答案(除非结果证明是错误的,或者另一个用户的新答案要好得多,但这里不是这种情况,OP 清楚地考虑了原答案正确)。我只能假设 OP 不知道这种情况下的指导方针。
  • @Abel 我知道这些指导方针,但我觉得有必要发布我自己的答案,因为我的答案“比这里的所有答案都好得多”,因为它是唯一的答案:1.包括如何指定压缩编解码器(根据 OP 中的要求),2. 包括如何将其添加为 pimp/extension 方法(根据 OP 中的要求),3. 实际编译! (包括必要的导入),4. 使用正确的 Scala 样式和格式。可悲的是,快到 2019 年了,并不是每个人都可以编写可编译的代码,甚至不是正确的样式。
  • 最佳答案其实是最好的,看来你基本上是抄他的。
  • @JPSilvashy 我确实尝试编辑答案,以便它 1. 包括如何指定压缩编解码器(按照 OP 中的要求),2. 包括如何将其添加为皮条客/扩展方法(按照OP中的要求),3.实际编译! (包括必要的导入),4. 使用正确的 Scala 样式和格式。发帖人拒绝了我的编辑,所以我创建了一个新答案。至少有十几个人发现我的答案比最佳答案更有帮助。
【解决方案5】:

我有类似的需求并找到了方法。但它有一个缺点(对我来说这不是问题):您需要对数据重新分区,每个输出文件只有一个分区。

要以这种方式进行分区,通常需要事先知道作业将输出多少个文件,并找到将每个键映射到每个分区的函数。

首先让我们创建基于 MultipleTextOutputFormat 的类:

import org.apache.hadoop.mapred.lib.MultipleTextOutputFormat

class KeyBasedOutput[T >: Null, V <: AnyRef] extends MultipleTextOutputFormat[T , V] {
  override def generateFileNameForKeyValue(key: T, value: V, leaf: String) = {
    key.toString
  }
  override protected def generateActualKey(key: T, value: V) = {
    null
  }
}

有了这个类,Spark 会从一个分区(我猜是第一个/最后一个)获取一个键,并用这个键命名文件,所以在同一个分区上混合多个键是不好的。

对于您的示例,您将需要一个自定义分区程序。这将完成这项工作:

import org.apache.spark.Partitioner

class IdentityIntPartitioner(maxKey: Int) extends Partitioner {
  def numPartitions = maxKey

  def getPartition(key: Any): Int = key match {
    case i: Int if i < maxKey => i
  }
}

现在让我们把所有东西放在一起:

val rdd = sc.makeRDD(Seq((1, "a"), (1, "b"), (2, "c"), (7, "d"), (7, "e")))

// You need to know the max number of partitions (files) beforehand
// In this case we want one partition per key and we have 3 keys,
// with the biggest key being 7, so 10 will be large enough
val partitioner = new IdentityIntPartitioner(10)

val prefix = "hdfs://.../prefix"

val partitionedRDD = rdd.partitionBy(partitioner)

partitionedRDD.saveAsHadoopFile(prefix,
    classOf[Integer], classOf[String], classOf[KeyBasedOutput[Integer, String]])

这将在前缀下生成 3 个文件(名为 1、2 和 7),一次处理所有内容。

如您所见,您需要一些关于密钥的知识才能使用此解决方案。

对我来说,这更容易,因为我需要为每个密钥哈希生成一个输出文件,并且文件的数量在我的控制之下,所以我可以使用库存的 HashPartitioner 来解决问题。

【讨论】:

  • 这无疑是迄今为止最好的解决方案,而且似乎几乎可以解决问题。我有点担心这会导致每个键生成一个文件,这会导致大型数据集出现问题。如果您可以修改您的答案,以便每个键的输出文件数是可配置的,我将非常感激。
  • @samthebest,我可以做到,但这将是一个非常具体的解决方案。您能否更新问题以说明您希望每个键有多个输出文件?顺便问一下,你真的在​​工作中使用整数键吗?
  • 好吧,任何对分区有意义的键 - 所以当我们调用 toString 时这是合理的。我不确定我是否需要更新我的答案,因为在 HDFS 上生成大文件是众所周知的不良做法,因为它限制了您可以使用的压缩类型。如果我们有非常大的文件,我们必须选择一个可拆分的压缩算法,这可能不适合手头的工作。此外,由于 Hadoop 中的错误,Spark 目前无法读取 bzip2(我最喜欢的可拆分压缩)。不过,我会更新我的答案以明确。再次,非常感谢。
  • 这个解决方案把所有的数据放在一个节点上,如果它们都有相同的密钥,对吗?似乎不利于它的一般可扩展性。
  • @DanielDarabos 点是正确的。当然可以调整IdentityIntPartitioner,以便对于每个可能的键都有几个分区,比如M,其中一个是随机选择的。我们需要使用散列函数并以numPartitions 取模,但会出现一个问题——不同的键可能会出现在同一个分区中,我假设这会破坏saveAsHadoopFile?这是一个不平凡的问题。
【解决方案6】:

我在 Java 中也需要同样的东西。将我对Zhang Zhan's Scala answer 的翻译发布给 Spark Java API 用户:

import org.apache.hadoop.mapred.lib.MultipleTextOutputFormat;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaSparkContext;
import scala.Tuple2;

import java.util.Arrays;


class RDDMultipleTextOutputFormat<A, B> extends MultipleTextOutputFormat<A, B> {

    @Override
    protected String generateFileNameForKeyValue(A key, B value, String name) {
        return key.toString();
    }
}

public class Main {

    public static void main(String[] args) {
        SparkConf conf = new SparkConf()
                .setAppName("Split Job")
                .setMaster("local");
        JavaSparkContext sc = new JavaSparkContext(conf);
        String[] strings = {"Abcd", "Azlksd", "whhd", "wasc", "aDxa"};
        sc.parallelize(Arrays.asList(strings))
                // The first character of the string is the key
                .mapToPair(s -> new Tuple2<>(s.substring(0,1).toLowerCase(), s))
                .saveAsHadoopFile("output/", String.class, String.class,
                        RDDMultipleTextOutputFormat.class);
        sc.stop();
    }
}

【讨论】:

    【解决方案7】:

    saveAsText() 和 saveAsHadoop(...) 是基于 RDD 数据实现的,特别是通过方法:PairRDD.saveAsHadoopDataset 从执行它的 PairRdd 中获取数据。 我看到了两种可能的选择:如果您的数据相对较小,您可以通过对 RDD 进行分组、从每个集合创建一个新的 RDD 并使用该 RDD 写入数据来节省一些实施时间。像这样的:

    val byKey = dataRDD.groupByKey().collect()
    val rddByKey = byKey.map{case (k,v) => k->sc.makeRDD(v.toSeq)}
    val rddByKey.foreach{ case (k,rdd) => rdd.saveAsText(prefix+k}
    

    请注意,它不适用于大型数据集,因为 v.toSeq 处的迭代器的具体化可能不适合内存。

    我看到的另一个选项,实际上我在这种情况下推荐的选项是:通过直接调用 hadoop/hdfs api 自己动手。

    这是我在研究这个问题时开始的讨论: How to create RDDs from another RDD?

    【讨论】:

    • 是的,我想使用 hadoop/hdfs api - 即使用 MultipleOutputFormat,但我想知道如何做到这一点。
    • 你不能在另一个 RDD(你的第二行)中创建一个 RDD。看这个pptslideshare.net/databricks/…
    • @Adrian 你是对的。我错过了那里的收藏。
    【解决方案8】:

    我有一个类似的用例,我将 Hadoop HDFS 上的输入文件基于一个键(每个键 1 个文件)拆分为多个文件。这是我的 spark scala 代码

    import org.apache.hadoop.conf.Configuration;
    import org.apache.hadoop.fs.FileSystem;
    import org.apache.hadoop.fs.Path;
    
    val hadoopconf = new Configuration();
    val fs = FileSystem.get(hadoopconf);
    
    @serializable object processGroup {
        def apply(groupName:String, records:Iterable[String]): Unit = {
            val outFileStream = fs.create(new Path("/output_dir/"+groupName))
            for( line <- records ) {
                    outFileStream.writeUTF(line+"\n")
                }
            outFileStream.close()
        }
    }
    val infile = sc.textFile("input_file")
    val dateGrouped = infile.groupBy( _.split(",")(0))
    dateGrouped.foreach( (x) => processGroup(x._1, x._2))
    

    我已根据键对记录进行分组。每个键的值被写入单独的文件。

    【讨论】:

    • 这看起来是一个很好的解决方案,特别是因为它处理结果迭代,我得到一个 org.apache.spark.SparkException: Task not serializable,你认为 fs 实例导致这个有问题吗?
    • 我喜欢这个解决方案,因为它不使用 DataFrames。这个对我有用。我担心它每组只写 1 个文件,这对于大型数据集来说可能很麻烦,对吧?例如,我的组大约 150MB,这很好......
    • 我认为这个解决方案不适用于每个键中的大量数据
    【解决方案9】:

    如果您有多个列并且想要保存所有其他未以 csv 格式分区的列,这对 python 用户来说是个好消息,如果您使用“文本”方法作为 Nick Chammas 的建议,这将失败。

    people_df.write.partitionBy("number").text("people") 
    

    错误信息是“AnalysisException: u'Text 数据源只支持单列,而你有2列。;'”

    在spark 2.0.0(我的测试环境是hdp的spark 2.0.0)包“com.databricks.spark.csv”现在已经集成,它允许我们保存仅由一列分区的文本文件,参见示例:

    people_rdd = sc.parallelize([(1,"2016-12-26", "alice"),
                                 (1,"2016-12-25", "alice"),
                                 (1,"2016-12-25", "tom"), 
                                 (1, "2016-12-25","bob"), 
                                 (2,"2016-12-26" ,"charlie")])
    df = people_rdd.toDF(["number", "date","name"])
    
    df.coalesce(1).write.partitionBy("number").mode("overwrite").format('com.databricks.spark.csv').options(header='false').save("people")
    
    [root@namenode people]# tree
    .
    ├── number=1
    │?? └── part-r-00000-6bd1b9a8-4092-474a-9ca7-1479a98126c2.csv
    ├── number=2
    │?? └── part-r-00000-6bd1b9a8-4092-474a-9ca7-1479a98126c2.csv
    └── _SUCCESS
    
    [root@namenode people]# cat number\=1/part-r-00000-6bd1b9a8-4092-474a-9ca7-1479a98126c2.csv
    2016-12-26,alice
    2016-12-25,alice
    2016-12-25,tom
    2016-12-25,bob
    [root@namenode people]# cat number\=2/part-r-00000-6bd1b9a8-4092-474a-9ca7-1479a98126c2.csv
    2016-12-26,charlie
    

    在我的 spark 1.6.1 环境中,代码没有抛出任何错误,但是只生成了一个文件。它没有被两个文件夹分割。

    希望这能有所帮助。

    【讨论】:

      【解决方案10】:

      我有一个类似的用例。我通过编写两个实现MultipleTextOutputFormatRecordWriter 的自定义类在Java 中解决了这个问题。

      我的输入是JavaPairRDD&lt;String, List&lt;String&gt;&gt;,我想将它存储在一个以它的键命名的文件中,所有行都包含在它的值中。

      这是我的MultipleTextOutputFormat 实现的代码

      class RDDMultipleTextOutputFormat<K, V> extends MultipleTextOutputFormat<K, V> {
      
          @Override
          protected String generateFileNameForKeyValue(K key, V value, String name) {
              return key.toString(); //The return will be used as file name
          }
      
          /** The following 4 functions are only for visibility purposes                 
          (they are used in the class MyRecordWriter) **/
          protected String generateLeafFileName(String name) {
              return super.generateLeafFileName(name);
          }
      
          protected V generateActualValue(K key, V value) {
              return super.generateActualValue(key, value);
          }
      
          protected String getInputFileBasedOutputFileName(JobConf job,     String name) {
              return super.getInputFileBasedOutputFileName(job, name);
              }
      
          protected RecordWriter<K, V> getBaseRecordWriter(FileSystem fs, JobConf job, String name, Progressable arg3) throws IOException {
              return super.getBaseRecordWriter(fs, job, name, arg3);
          }
      
          /** Use my custom RecordWriter **/
          @Override
          RecordWriter<K, V> getRecordWriter(final FileSystem fs, final JobConf job, String name, final Progressable arg3) throws IOException {
          final String myName = this.generateLeafFileName(name);
              return new MyRecordWriter<K, V>(this, fs, job, arg3, myName);
          }
      } 
      

      这是我的RecordWriter 实现的代码。

      class MyRecordWriter<K, V> implements RecordWriter<K, V> {
      
          private RDDMultipleTextOutputFormat<K, V> rddMultipleTextOutputFormat;
          private final FileSystem fs;
          private final JobConf job;
          private final Progressable arg3;
          private String myName;
      
          TreeMap<String, RecordWriter<K, V>> recordWriters = new TreeMap();
      
          MyRecordWriter(RDDMultipleTextOutputFormat<K, V> rddMultipleTextOutputFormat, FileSystem fs, JobConf job, Progressable arg3, String myName) {
              this.rddMultipleTextOutputFormat = rddMultipleTextOutputFormat;
              this.fs = fs;
              this.job = job;
              this.arg3 = arg3;
              this.myName = myName;
          }
      
          @Override
          void write(K key, V value) throws IOException {
              String keyBasedPath = rddMultipleTextOutputFormat.generateFileNameForKeyValue(key, value, myName);
              String finalPath = rddMultipleTextOutputFormat.getInputFileBasedOutputFileName(job, keyBasedPath);
              Object actualValue = rddMultipleTextOutputFormat.generateActualValue(key, value);
              RecordWriter rw = this.recordWriters.get(finalPath);
              if(rw == null) {
                  rw = rddMultipleTextOutputFormat.getBaseRecordWriter(fs, job, finalPath, arg3);
                  this.recordWriters.put(finalPath, rw);
              }
              List<String> lines = (List<String>) actualValue;
              for (String line : lines) {
                  rw.write(null, line);
              }
          }
      
          @Override
          void close(Reporter reporter) throws IOException {
              Iterator keys = this.recordWriters.keySet().iterator();
      
              while(keys.hasNext()) {
                  RecordWriter rw = (RecordWriter)this.recordWriters.get(keys.next());
                  rw.close(reporter);
              }
      
              this.recordWriters.clear();
          }
      }
      

      大部分代码与FileOutputFormat 中的完全相同。唯一的区别就是那几行

      List<String> lines = (List<String>) actualValue;
      for (String line : lines) {
          rw.write(null, line);
      }
      

      这些行允许我在文件上写下我输入List&lt;String&gt; 的每一行。 write 函数的第一个参数设置为null 以避免在每一行上写键。

      要完成,我只需要执行这个调用来编写我的文件

      javaPairRDD.saveAsHadoopFile(path, String.class, List.class, RDDMultipleTextOutputFormat.class);
      

      【讨论】:

        猜你喜欢
        • 2015-10-19
        • 1970-01-01
        • 1970-01-01
        • 2021-09-18
        • 1970-01-01
        • 2015-08-31
        相关资源
        最近更新 更多