我正在使用scala 2.7.7,并希望解析CSV文件并将数据存储在SQLite数据库中。
我最终使用OpenCSV java库来解析CSV文件,并使用sqlitejdbc库。
使用这些java库使我的scala代码看起来几乎与Java代码相同(没有分号和val / var)
当我处理java对象时,我不能使用scala list,map等,除非我做scala2java转换或升级到scala 2.8
有没有办法可以使用我不知道的scala位进一步简化代码?
val filename = "file.csv";
val reader = new CSVReader(new FileReader(filename))
var aLine = new Array[String](10)
var lastSymbol = ""
while( (aLine = reader.readNext()) != null ) {
if( aLine != null ) {
val symbol = aLine(0)
if( !symbol.equals(lastSymbol)) {
try {
val rs = stat.executeQuery("select name from sqlite_master where name='" + symbol + "';" )
if( !rs.next() ) {
stat.executeUpdate("drop table if exists '" + symbol + "';")
stat.executeUpdate("create table '" + symbol + "' (symbol,data,open,high,low,close,vol);")
}
}
catch {
case sqle : java.sql.SQLException =>
println(sqle)
}
lastSymbol = symbol
}
val prep = conn.prepareStatement("insert into '" + symbol + "' values (?,?,?,?,?,?,?);")
prep.setString(1, aLine(0)) //symbol
prep.setString(2, aLine(1)) //date
prep.setString(3, aLine(2)) //open
prep.setString(4, aLine(3)) //high
prep.setString(5, aLine(4)) //low
prep.setString(6, aLine(5)) //close
prep.setString(7, aLine(6)) //vol
prep.addBatch()
prep.executeBatch()
}
}
conn.close()
答案 0 :(得分:9)
如果您有一个简单的CSV文件,则可以选择根本不使用任何CSV库,只需在Scala中解析它,例如:
case class Stock(line: String) {
val data = line.split(",")
val date = data(0)
val open = data(1).toDouble
val high = data(2).toDouble
val low = data(3).toDouble
val close = data(4).toDouble
val volume = data(5).toDouble
val adjClose = data(6).toDouble
def price: Double = low
}
scala> import scala.io._
scala> Source.fromFile("stock.csv") getLines() map (l => Stock(l))
res0: Iterator[Stock] = non-empty iterator
scala> res0.toSeq
res1: Seq[Stock] = List(Stock(2010-03-15,37.90,38.04,37.42,37.64,941500,37.64), Stock(2010-03-12,38.00,38.08,37.66,37.89,834800,37.89) //etc...
这样可以使用完整的Scala集合API。
如果您更喜欢使用解析器组合器,那么github上还有一个csv parser combinator的示例。
答案 1 :(得分:3)
if
之后的while
语句无效 - 您已确保aLine
不为空。
另外,我不确切知道aLine
的内容到底是什么,但你可能想做类似的事情
aLine.zipWithIndex.foreach(i => prep.setString(i._2+1 , i._1))
而不是从1到7手动计数。或者,你可以
for (i <- 1 to 7) { prep.setString(i, aLine(i)) }
如果您觉得采用更具功能性的风格,可以用
代替whileIterator.continually(reader.readNext()).takeWhile(_!=null).foreach(aLine => {
// Body of while goes here
}
(并删除var aLine)。但使用while可以。也可以重构以避免lastSymbol(例如通过使用递归def),但我不确定这是值得的。
答案 2 :(得分:3)
如果你想在Scala中解析它,内置的解析器是非常强大的,一旦你掌握它,很容易。我不是专家,但通过一些规范测试,证明这是有用的:
object CSVParser extends RegexParsers {
def apply(f: java.io.File): Iterator[List[String]] = io.Source.fromFile(f).getLines().map(apply(_))
def apply(s: String): List[String] = parseAll(fromCsv, s) match {
case Success(result, _) => result
case failure: NoSuccess => {throw new Exception("Parse Failed")}
}
def fromCsv:Parser[List[String]] = rep1(mainToken) ^^ {case x => x}
def mainToken = (doubleQuotedTerm | singleQuotedTerm | unquotedTerm) <~ ",?".r ^^ {case a => a}
def doubleQuotedTerm: Parser[String] = "\"" ~> "[^\"]+".r <~ "\"" ^^ {case a => (""/:a)(_+_)}
def singleQuotedTerm = "'" ~> "[^']+".r <~ "'" ^^ {case a => (""/:a)(_+_)}
def unquotedTerm = "[^,]+".r ^^ {case a => (""/:a)(_+_)}
override def skipWhitespace = false
}
这可能不是我认为的功能完整解决方案,我不是它如何处理UTF-8等,但它似乎适用于至少有引号的ASCII CSV。
答案 3 :(得分:2)
如果你想要一些更具惯用性且更安全的东西,我可以建议kantan.csv吗?
它允许您将任何CSV数据源转换为良好类型值的集合。要重写示例的CSV解析部分(并将日期作为字符串处理,因为我不知道你收到它们的格式),你要写:
import kantan.csv.ops._
import kantan.csv.generic.codecs._
case class Symbol(name: String, date: String, open: Double, high: Double, low: Double, close: Double, vol: Double)
def sqliteMagic(symbol: Symbol): Unit = ???
new File(filename).asUnsafeCsvRows[Symbol](',', false).foreach(sqliteMagic)
请注意,当您可以使用更具体的类型时,我并不特别喜欢使用元组。使用kantan.csv的shapeless模块,您可以更好地编写它:
Symbol
请注意,您不必为支持var
案例类做任何工作,这非常好,并且由于无形而仍然是类型安全的。
完全披露:我是kantan.csv的作者。