【问题标题】:How to write to a csv file in scala?如何在scala中写入csv文件?
【发布时间】:2019-03-10 23:56:26
【问题描述】:

我正在尝试将数据写入 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 行相同的值(第一行值),列表中的值没有得到迭代。

任何参考资料也会有所帮助

【问题讨论】:

  • 什么是csvwriter?它不是 Scala 的标准部分,因此您要么使用库(哪个库?),要么您已经编写了它。
  • 没有。我已经使用了这个库“ libraryDependencies += "au.com.bytecode" % "opencsv" % "2.4"
  • 您已经定义了val output,但作为CSVWriter(outputFile) 传递给了CSVWriter 构造函数。对吗?
  • 你使用了val output = new BufferedReader(new FileWriter("Resulet.csv")。为什么要将FileWriter object 传递给BufferedReader object
  • 你可以使用apache commons csv包来创建csv文件。

标签: java scala list collections


【解决方案1】:

如果没有完整的示例(如在编译 Main 文件中),就不能说为什么您一遍又一遍地得到相同的行。您发布的 sn-p 是正确的。

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 的原因。这样做的原因是遍历结构时堆栈如何工作的工件,超出了这个问题的范围,但是有很多关于为什么使用foldLeftfoldRight 的好文章。

根据我上面读到的内容,这就是示例中您的行的样子。

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 文件

现在开始编写 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
    }
  )

让我们暂时分解一下。我正在使用来自scala.util 包的Try 数据类型。它类似于语言级别的try/catch/finally 块,但不是使用特殊构造来捕获异常,而是使用普通值。这是 Scala 中的另一个常见习语,更喜欢简单的语言值而不是特殊的语言控制流构造。

让我们仔细看看这个表达式(header +: rows).map(_.toArray).asJava。这个小表达式做了很多操作。首先,我们将header 行添加到行列表(header +: rows) 的前面。然后,由于CSVWriter 需要Iterable&lt;Array&lt;String&gt;&gt;,我们首先将内部类型转换为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"

最后说明

过时的库

我在原始帖子的 cmets 中注意到您使用的是 "au.com.bytecode" % "opencsv" % "2.4"。我一般不熟悉 opencsv 库,但根据 Maven Central 的说法,这似乎是主存储库的一个非常古老的分支。我建议你使用主仓库。 https://search.maven.org/search?q=opencsv

性能

人们经常担心,在使用不可变数据结构和技术时,我们需要在性能上进行权衡。这可能是这种情况,但通常渐近复杂度是不变的。上面的解决方案是O(n),其中n 是行数。它具有比可变解决方案更高的常数,但通常这并不重要。如果是这样,可以使用一些技术,例如在addPrefix 中更明确的递归可以缓解这种情况。然而,除非你真的需要,否则你永远不应该这样优化,因为这会使代码更容易出错且更不惯用(因此更不可读)。

【讨论】:

  • 感谢您的帮助。但预期的输出应该就像你用 LIST(1,2,3,4) 正确创建了一个列表。第一个值 1 应该填充第一个单元格,下一个单元格中列表中的第二个值等等。不应将整个列表填充到单元格中
  • 我想这可能就是你的意思。我已经使用每个列值的每个列表更新了我的答案。请注意,唯一改变的是初始数据,因为在一天结束时,一行始终是 List[String]
  • 你能解释一下吗? - 我从其他方法调用 writeCSVFile() 方法,只有第一行被填充,标题和第一行。 .
  • 在 writeCSVFile() 方法中写入时,我没有转换为 Java,这会是它的原因吗?即使我已经导入了 JavaConversions_,我也得到了 asJava is not a member of List[String]
  • @PrishiKumar 你想导入scala.collection.JavaConverters._ 而不是JavaConversionsJavaConversions 进行自动隐式转换,通常被认为是一种不好的做法,因为它隐藏了代码的语义。
猜你喜欢
  • 2011-06-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-15
  • 2018-07-29
  • 2019-01-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多