Scala Spark循环没有任何错误,但不会产生输出

时间:2017-11-08 20:49:41

标签: scala hadoop apache-spark hdfs

我在HDFS中有一个包含各种其他文件路径的文件。这是名为file1的文件:

path/of/HDFS/fileA
path/of/HDFS/fileB
path/of/HDFS/fileC
.
.
.

我在Scala Spark中使用for循环,如下所示,读取上述文件的每一行并在另一个函数中处理它:

val lines=Source.fromFile("path/to/file1.txt").getLines.toList

for(i<-lines){
i.toString()
val firstLines=sc.hadoopFile(i,classOf[TextInputFormat],classOf[LongWritable],classOf[Text]).flatMap {
case (k, v) => if (k.get == 0) Seq(v.toString) else Seq.empty[String]
}
}

当我运行上面的循环时,它会在没有返回任何错误的情况下运行,并在新行中显示Scala提示符:scala&gt;

但是,当我尝试查看应存储在firstLines中的几行输出时,它不起作用:

scala> firstLines
<console>:38: error: not found: value firstLines
          firstLine
          ^

上述循环中没有产生输出的问题是什么,但是没有任何错误地运行?

其他信息 函数hadoopFile接受String路径名作为其第一个参数。这就是为什么我试图在第一个参数i中将每行file1(每行是路径名)作为String传递。 flatMap功能将传递给hadoopFile的文件的第一行单独存储并转储所有其他行。因此,所需的输出(firstLines)应该是通过其路径名(i)传递给hadoopFile的所有文件的第一行。

我尝试只为一个文件运行该函数,没有looop,并产生输出:

val firstLines=sc.hadoopFile("path/of/HDFS/fileA",classOf[TextInputFormat],classOf[LongWritable],classOf[Text]).flatMap {
case (k, v) => if (k.get == 0) Seq(v.toString) else Seq.empty[String]
}

scala> firstLines.take(3)
res27: Array[String] = Array(<?xml version="1.0" encoding="utf-8"?>)

fileA是一个XML文件,因此您可以看到该文件生成的第一行。所以我知道函数工作正常,这只是我无法弄清楚的循环问题。请帮忙。

1 个答案:

答案 0 :(得分:1)

变量firstLinesfor循环的主体中定义,因此其范围仅限于此循环。这意味着您无法访问循环外的变量,这就是Scala编译器告诉您error: not found: value firstLines的原因。

根据您的说明,我了解您要收集lines中列出的每个文件的第一行。

这里的每个都可以转换为Scala中的不同构造。我们可以使用您编写的for循环之类的东西,甚至可以更好地采用函数方法并使用应用于文件列表的map函数。在下面的代码中,我在您的描述中使用了map代码,其中创建了HadoopRDD并将flatMap与您的函数一起应用于检索文件的第一行。

然后我们获得RDD[String]行的列表。在这个阶段,请注意我们还没有开始做任何实际的工作。要触发RDD的评估并收集结果,我们需要为列表中的每个RDD添加collect方法。

// Renamed "lines" to "files" as it is more explicit.  
val fileNames = Source.fromFile("path/to/file1.txt").getLines.toList

val firstLinesRDDs = fileNames.map(sc.hadoopFile(_,classOf[TextInputFormat],classOf[LongWritable],classOf[Text]).flatMap {
  case (k, v) => if (k.get == 0) Seq(v.toString) else Seq.empty[String]
})

// firstLinesRDDs is a list of RDD[String]. Based on this code, each RDD
// should consist in a single String value. We collect them using RDD#collect:
val firstLines = firstLinesRDDs.map(_.collect)

然而,这种方法存在一个缺陷,使我们无法从Spark可以提供的任何优势中受益。

当我们将map中的操作应用于filenames时,我们不使用RDD,因此文件名在驱动程序(承载Spark会话的进程)上按顺序处理,而不是可并行化Spark工作的一部分。这相当于您在第二个代码块中编写的内容,一次一个文件名。

要解决这个问题,我们能做些什么?使用Spark时要记住的一件好事是尝试在代码中尽早推送RDD的声明。为什么?因为这允许Spark并行化并优化我们想要做的工作。您的示例可能是此概念的教科书示例,但此处的操作文件要求会增加额外的复杂性。

在我们目前的情况下,我们可以从hadoopFile接受输入中逗号分隔文件的事实中受益。因此,我们不是为每个文件顺序创建RDD,而是为所有文件创建一个RDD:

val firstLinesRDD = sc.hadoopFile(fileNames.mkString(","), classOf[TextInputFormat],classOf[LongWritable],classOf[Text]).flatMap {
  case (k, v) => if (k.get == 0) Seq(v.toString) else Seq.empty[String]
}

我们使用单个collect检索我们的第一行:

val firstLines = firstLinesRDD.collect