第15章——使用Scala创建应用程序
在本章中,我们将会把在本书中学到的许多东西汇集到一起,并学习一些新东西。我们 将逐步构建一个应用程序,用于找到股票市场中的投资的净值。在这个练习中,我们将会看 到一些夺目的特性:简洁性与表现力、模式匹配的力量以及函数值/闭包和并发。此外,我们 还将学习 Scala 对 XML 处理的支持—一个在构建企业级应用时会极大受益的特性。 ①
- 从 Scala 2.11.x 版本开始,对 XML 的支持已经被移到了单独的模块中。——译者注
15.1 获取用户输入
UsingScala/ConsoleInput.scala
import scala.io._
print("Please enter a ticker symbol:")
val symbol = StdIn.readLine()
println(s"OK, got it, you own $symbol")
运行结果
Please enter a ticker symbol:OK, got it, you own AAPL
15.2 读写文件
UsingScala/WriteToFile.scala
import java.io._
val writer = new PrintWriter(new File("symbols.txt"))
writer.write("AAPL")
writer.close()
println(scala.io.Source.fromFile("symbols.txt").mkString)
UsingScala/ReadingFile.scala
import scala.io.Source
println("*** The content of the file you read is:")
Source.fromFile("ReadingFile.scala").foreach { print }
运行结果
*** The content of the file you read is:
import scala.io.Source
println("*** The content of the file you read is:")
Source.fromFile("ReadingFile.scala").foreach { print }
UsingScala/ReadingURL.scala
import scala.io.Source
import java.net.URL
val source = Source.fromURL(new URL("http://localhost"))
println(s"What's Source?: $source")
println(s"Raw String: ${source.mkString}")
运行结果
What's Source?: non-empty iterator
Raw String: <html><body><h1>It works!</h1></body></html>
15.3 XML 作为一等公民
UsingScala/UseXML.scala
val xmlFragment =
<symbols>
<symbol ticker="AAPL"><units>200</units></symbol>
<symbol ticker="IBM"><units>215</units></symbol>
</symbols>
println(xmlFragment)
println(xmlFragment.getClass)
运行结果
<symbols>
<symbol ticker="AAPL"><units>200</units></symbol>
<symbol ticker="IBM"><units>215</units></symbol>
</symbols>
class scala.xml.Elem
UsingScala/UseXML.scala
var symbolNodes = xmlFragment \ "symbol"
symbolNodes.foreach(println)
println(symbolNodes.getClass)
运行结果
<symbol ticker="AAPL"><units>200</units></symbol>
<symbol ticker="IBM"><units>215</units></symbol>
class scala.xml.NodeSeq$$anon$1
UsingScala/UseXML.scala
var unitsNodes = xmlFragment \\ "units"
unitsNodes.foreach(println)
println(unitsNodes.getClass)
println(unitsNodes.head.text)
运行结果
<units>200</units>
<units>215</units>
class scala.xml.NodeSeq$$anon$1
200
UsingScala/UseXML.scala
unitsNodes.head match {
case <units>{numberOfUnits}</units> => println(s"Units: $numberOfUnits")
}
运行结果
Units: 200
UsingScala/UseXML.scala
println("Ticker\tUnits")
xmlFragment match {
case <symbols>{symbolNodes @ _*}</symbols> =>
for (symbolNode @ <symbol>{_*}</symbol> <- symbolNodes) {
println("%-7s %s".format(symbolNode \ "@ticker", (symbolNode \ "units").text))
}
}
运行结果
Ticker Units
AAPL 200
IBM 215
15.4 读写 XML
stocks.xml
<symbols>
<symbol ticker="AAPL"><units>200</units></symbol>
<symbol ticker="ADBE"><units>125</units></symbol>
<symbol ticker="ALU"><units>150</units></symbol>
<symbol ticker="AMD"><units>150</units></symbol>
<symbol ticker="CSCO"><units>250</units></symbol>
<symbol ticker="HPQ"><units>225</units></symbol>
<symbol ticker="IBM"><units>215</units></symbol>
<symbol ticker="INTC"><units>160</units></symbol>
<symbol ticker="MSFT"><units>190</units></symbol>
<symbol ticker="NSM"><units>200</units></symbol>
<symbol ticker="ORCL"><units>200</units></symbol>
<symbol ticker="SYMC"><units>230</units></symbol>
<symbol ticker="TXN"><units>190</units></symbol>
<symbol ticker="VRSN"><units>200</units></symbol>
<symbol ticker="XRX"><units>240</units></symbol>
</symbols>
UsingScala/ReadWriteXML.scala
import scala.xml._
val stocksAndUnits = XML.load("stocks.xml")
println(stocksAndUnits.getClass)
println(s"File has ${(stocksAndUnits \\ "symbol").size} symbol elements")
运行结果
class scala.xml.Elem
File has 15 symbol elements
UsingScala/ReadWriteXML.scala
val stocksAndUnitsMap =
(Map[String, Int]() /: (stocksAndUnits \ "symbol")) { (map, symbolNode) =>
val ticker = (symbolNode \ "@ticker").toString
val units = (symbolNode \ "units").text.toInt
map + (ticker -> units) //return new map, with one additional entry
}
println(s"Number of symbol elements found is ${stocksAndUnitsMap.size}")
运行结果
Number of symbol elements found is 15
UsingScala/ReadWriteXML.scala
val updatedStocksAndUnitsXML =
<symbols>
{stocksAndUnitsMap.map(updateUnitsAndCreateXML)}
</symbols>
def updateUnitsAndCreateXML(element: (String, Int)) = {
val (ticker, units) = element
<symbol ticker={ticker}>
<units>{units + 1}</units>
</symbol>
}
XML.save("stocks2.xml", updatedStocksAndUnitsXML)
val elementsCount = (XML.load("stocks2.xml") \\ "symbol").size
println(s"Saved file has $elementsCount symbol elements")
运行结果
Saved file has 15 symbol elements
示例文件
Date,Open,High,Low,Close,Volume,Adj Close
2015-03-20,561.65,561.72,559.05,560.36,2585800,560.36
2015-03-19,559.39,560.80,556.15,557.99,1191100,557.99
2015-03-18,552.50,559.78,547.00,559.50,2124400,559.50
...
15.5 从 Web 获取股票价格
UsingScala/StockPriceFinder.scala
object StockPriceFinder {
import scala.io.Source
case class Record(year: Int, month: Int, date: Int, closePrice: BigDecimal)
def getLatestClosingPrice(symbol: String): BigDecimal = {
val url = s"https://raw.githubusercontent.com/ReactivePlatform/" +
s"Pragmatic-Scala-StaticResources/master/src/main/resources/" +
s"stocks/daily/daily_$symbol.csv"
val data = Source.fromURL(url).mkString
val latestClosePrize = data
.split("\n")
.slice(1, 2)
.map(record => {
val Array(timestamp, open, high, low, close, volume) = record.split(",")
val Array(year, month, date) = timestamp.split("-")
Record(year.toInt, month.toInt, date.toInt, BigDecimal(close.trim))
})
.map(_.closePrice)
.head
latestClosePrize
}
def getTickersAndUnits: Map[String, Int] = {
val classLoader = this.getClass.getClassLoader
val stocksXMLInputStream = classLoader.getResourceAsStream("stocks.xml")
//或者来自于文件
val stocksAndUnitsXML = scala.xml.XML.load(stocksXMLInputStream)
(Map[String, Int]() /: (stocksAndUnitsXML \ "symbol")) { (map, symbolNode) =>
val ticker = (symbolNode \ "@ticker").toString
val units = (symbolNode \ "units").text.toInt
map + (ticker -> units)
}
}
}
UsingScala/FindTotalWorthSequential.scala
object FindTotalWorthSequential extends App {
val symbolsAndUnits = StockPriceFinder.getTickersAndUnits
println("Ticker Units Closing Price($) Total Value($)")
val startTime = System.nanoTime()
val valuesAndWorth = symbolsAndUnits.keys.map { symbol =>
val units = symbolsAndUnits(symbol)
val latestClosingPrice = StockPriceFinder.getLatestClosingPrice(symbol)
val value = units * latestClosingPrice
(symbol, units, latestClosingPrice, value)
}
val netWorth = (BigDecimal(0.0d) /: valuesAndWorth) { (worth, valueAndWorth) =>
val (_, _, _, value) = valueAndWorth
worth + value
}
val endTime = System.nanoTime()
valuesAndWorth.toList.sortBy(_._1).foreach { valueAndWorth =>
val (symbol, units, latestClosingPrice, value) = valueAndWorth
println(f"$symbol%7s $units%5d $latestClosingPrice%15.2f $value%.2f")
}
println(f"The total value of your investments is $$$netWorth%.2f")
println(f"Took ${(endTime - startTime) / 1000000000.0}%.2f seconds")
}
运行结果
Ticker Units Closing Price($) Total Value($)
AAPL 200 125.90 25180.00
ADBE 125 77.36 9670.00
ALU 150 3.84 576.00
AMD 150 2.80 420.00
CSCO 250 28.44 7110.00
HPQ 225 33.28 7488.00
IBM 215 162.88 35019.20
INTC 160 31.31 5009.60
MSFT 190 42.88 8147.20
NSM 200 29.94 5988.00
ORCL 200 44.41 8882.00
SYMC 230 24.38 5607.40
TXN 190 59.28 11263.20
VRSN 200 64.75 12950.00
XRX 240 13.18 3163.20
The total value of your investments is $146473.80
Took 11.13 seconds
15.6 编写并发的资产净值应用程序
UsingScala/FindTotalWorthConcurrent.scala
import scala.collection.parallel.CollectionConverters._
val valuesAndWorth = symbolsAndUnits.keys.par.map { symbol =>
运行结果
Ticker Units Closing Price($) Total Value($)
AAPL 200 125.90 25180.00
ADBE 125 77.36 9670.00
ALU 150 3.84 576.00
AMD 150 2.80 420.00
CSCO 250 28.44 7110.00
HPQ 225 33.28 7488.00
IBM 215 162.88 35019.20
INTC 160 31.31 5009.60
MSFT 190 42.88 8147.20
NSM 200 29.94 5988.00
ORCL 200 44.41 8882.00
SYMC 230 24.38 5607.40
TXN 190 59.28 11263.20
VRSN 200 64.75 12950.00
XRX 240 13.18 3163.20
The total value of your investments is $146473.80
Took 1.98 seconds
1.0.0