我正在尝试将数据写入csv文件,我创建了四列,
val csvFields = Array("Serial Number", "Record Type", First File value", Second file value") ',
除序列号外,其他三个字段均为列表
Second_file_value = List ("B", "gjgbn", "fgbhjf", "dfjf")
First_File_Value = List ("A","abhc","agch","mknk")
Record_type = List('1','2',3','4');
val outputFile = new BufferedWriter(new FileWriter("Resulet.csv")
val csvWriter = new CSVWriter(outputFile)
val listOfRecords = new ListBuffer[Array[String]]()
listOfRecords :+ csvFields
我正在使用此循环写入列
for ( i <- 1 until 30){
listOfRecords += Array(i.toString, Record_type , First_File_Value , Second_file_value )}
csvWriter.writeAll(listOfRecords.toList)
output.close()
我面临的问题是csv文件中填充了30行相同的值(第一行值),列表中的值没有被迭代。
任何参考也将有帮助
答案 0 :(得分:0)
没有完整的示例(如在编译的Main
文件中),不能说为什么一遍又一遍地获得同一行。您发布的摘录正确无误。
scala> val lb: ListBuffer[Array[String]] = new ListBuffer[Array[String]]()
lb: scala.collection.mutable.ListBuffer[Array[String]] = ListBuffer()
scala> for (i <- 1 until 30){lb += Array(i.toString)}
scala> lb.toList
res5: List[Array[String]] = List(Array(1), Array(2), Array(3), Array(4), Array(5), Array(6), Array(7), Array(8), Array(9), Array(10), Array(11), Array(12), Array(13), Array(14), Array(15), Array(16), Array(17), Array(18), Array(19), Array(20), Array(21), Array(22), Array(23), Array(24), Array(25), Array(26), Array(27), Array(28), Array(29))
但是,通常有多种方法可以更好地做到这一点,这可能有助于您避免此错误和其他错误。
在Scala中,通常认为与可变结构相比,不可变结构是更好的习惯。鉴于此,我建议您构造一个函数,以使用不可变方法将串行前缀添加到行中。有多种方法可以执行此操作,但是最基本的方法是fold
操作。如果您不熟悉它,可以将fold
视为对结构的转换,例如for循环的功能版本。
请牢记这一点,这是您可能会采用的一些行,它们是List[List[String]]
并向所有行添加数字前缀。
def addPrefix(lls: List[List[String]]): List[List[String]] =
lls.foldLeft((1, List.empty[List[String]])){
// You don't need to annotate the types here, I just did that for clarity.
case ((serial: Int, acc: List[List[String]]), value: List[String]) =>
(serial + 1, (serial.toString +: value) +: acc)
}._2.reverse
一个foldLeft
会按照我们想要的顺序建立列表,这就是为什么我在末尾调用.reverse
的原因。造成这种情况的原因是关于在遍历结构时堆栈如何工作的人工产物,超出了此问题的范围,但是有很多关于为什么使用foldLeft
或foldRight
的文章。
根据我上面的阅读,这就是示例中的行。
val columnOne: List[String] =
List('1','2','3','4').map(_.toString)
val columnTwo: List[String] =
List("A","abhc","agch","mknk")
val columnThree: List[String] =
List("B", "gjgbn", "fgbhjf", "dfjf")
val rows: List[List[String]] =
columnOne.zip(columnTwo.zip(columnThree)).foldLeft(List.empty[List[String]]){
case (acc, (a, (b, c))) => List(a, b, c) +: acc
}.reverse
哪个会产生这个。
scala> rows.foreach(println)
List(1, A, B)
List(2, abhc, gjgbn)
List(3, agch, fgbhjf)
List(4, mknk, dfjf)
让我们尝试以该函数作为输入来调用我们的函数。
scala> addPrefix(rows).foreach(println)
List(1, 1, A, B)
List(2, 2, abhc, gjgbn)
List(3, 3, agch, fgbhjf)
List(4, 4, mknk, dfjf)
好的,看起来不错。
现在要写入CSV文件。由于CSVWriter
在Java集合类型方面有效,因此我们需要将Scala类型转换为Java集合。在Scala中,您应该在最后可能的时刻进行此操作。这样做的原因是Scala的类型设计为可以与Scala很好地配合使用,我们不希望早日失去这种能力。就不变性而言,它们也比并行Java类型更安全(如果您使用的是不变的变体,则本示例将这样做)。
让我们定义一个函数writeCsvFile
,该函数接受文件名,标题行和行列表并将其写出。同样,有很多方法可以正确地执行此操作,但这是一个简单的示例。
def writeCsvFile(
fileName: String,
header: List[String],
rows: List[List[String]]
): Try[Unit] =
Try(new CSVWriter(new BufferedWriter(new FileWriter(fileName)))).flatMap((csvWriter: CSVWriter) =>
Try{
csvWriter.writeAll(
(header +: rows).map(_.toArray).asJava
)
csvWriter.close()
} match {
case f @ Failure(_) =>
// Always return the original failure. In production code we might
// define a new exception which wraps both exceptions in the case
// they both fail, but that is omitted here.
Try(csvWriter.close()).recoverWith{
case _ => f
}
case success =>
success
}
)
让我们分解一下。我正在使用Try
包中的scala.util
数据类型。它类似于语言级别try/catch/finally
的块,但是它不是使用特殊的结构来捕获异常,而是使用常规值。这是Scala中的另一种常见习语,相对于特殊的语言控制流构造,更喜欢纯语言值。
让我们仔细看看这个表达式(header +: rows).map(_.toArray).asJava
。这个小表达式正在做很多操作。首先,我们将header
行添加到行(header +: rows)
的列表的前面。然后,由于CSVWriter
需要一个Iterable<Array<String>>
,我们首先将内部类型转换为Array
,然后将外部类型转换为Iterable
。 .asJava
调用是外部类型转换的工作,您可以通过导入scala.collection.JavaConverters._
来获得它,它在Scala和Java类型之间进行隐式转换。
该函数的其余部分非常简单。我们将行写出,然后检查是否失败。如果存在,我们确保仍尝试关闭CSVWriter
。
我在此处提供了完整的编译示例。
import com.opencsv._
import java.io._
import scala.collection.JavaConverters._
import scala.util._
object Main {
val header: List[String] =
List("Serial Number", "Record Type", "First File value", "Second file value")
val columnOne: List[String] =
List('1','2','3','4').map(_.toString)
val columnTwo: List[String] =
List("A","abhc","agch","mknk")
val columnThree: List[String] =
List("B", "gjgbn", "fgbhjf", "dfjf")
val rows: List[List[String]] =
columnOne.zip(columnTwo.zip(columnThree)).foldLeft(List.empty[List[String]]){
case (acc, (a, (b, c))) => List(a, b, c) +: acc
}.reverse
def addPrefix(lls: List[List[String]]): List[List[String]] =
lls.foldLeft((1, List.empty[List[String]])){
case ((serial: Int, acc: List[List[String]]), value: List[String]) =>
(serial + 1, (serial.toString +: value) +: acc)
}._2.reverse
def writeCsvFile(
fileName: String,
header: List[String],
rows: List[List[String]]
): Try[Unit] =
Try(new CSVWriter(new BufferedWriter(new FileWriter(fileName)))).flatMap((csvWriter: CSVWriter) =>
Try{
csvWriter.writeAll(
(header +: rows).map(_.toArray).asJava
)
csvWriter.close()
} match {
case f @ Failure(_) =>
// Always return the original failure. In production code we might
// define a new exception which wraps both exceptions in the case
// they both fail, but that is omitted here.
Try(csvWriter.close()).recoverWith{
case _ => f
}
case success =>
success
}
)
def main(args: Array[String]): Unit = {
println(writeCsvFile("/tmp/test.csv", header, addPrefix(rows)))
}
}
这是运行该程序后文件的内容。
"Serial Number","Record Type","First File value","Second file value"
"1","1","A","B"
"2","2","abhc","gjgbn"
"3","3","agch","fgbhjf"
"4","4","mknk","dfjf"
我在原始帖子的评论中注意到您正在使用"au.com.bytecode" % "opencsv" % "2.4"
。我一般不熟悉opencsv
库,但是据Maven Central称,这似乎是主存储库的一个很老的分支。我建议您使用主存储库。 https://search.maven.org/search?q=opencsv
人们经常担心,当使用不可变的数据结构和技术时,我们需要权衡性能。可以是这种情况,但是通常渐进复杂度不变。上面的解决方案是O(n)
,其中n
是行数。它具有比可变解决方案更高的常数,但通常并不重要。如果是这样,则可以采用某些技术,例如addPrefix
中更明确的递归可以减轻这种情况。但是,除非真正需要,否则就永远不要这样进行优化,因为这会使代码更容易出错,惯用性也较低(因此可读性更差)。