原文发表于:http://nerd-is.in/2013-08/scala-learning-files-and-regular-expressions
读取文件
1
2
3
4
5
6
7
8
9
|
import
scala
.
io
.
Source
val
source
=
Source
.
fromFile
(
fileName
,
"UTF-8"
)
// 第一个参数可以是文件名或java.io.File
// 如果没有第二个参数将会使用当前平台缺省的字符编码
val
lineIterator
=
source
.
getLines
// 结果是一个迭代器
// 迭代器可以转换成Array等
val
contents
=
source
.
mkString
// 整个文件读取成一个字符串
|
使用完Source后,记得需要close。
读取字符
要读取单个字符,可以直接从Source对象中进行迭代。Source类扩展自Iterator[Char]。
1
|
for
(
c
<
-
source
)
|
如果想要查看接下来的字符,但是不对其进行处理(也就是说迭代器不移动位置),可以调用Source的buffered方法,返回一个collection.BufferedIterator[Char],然后使用这个对象的head方法查看下一个字符。
1
2
3
4
5
6
7
8
9
10
11
|
val
source
=
Source
.
fromFile
(
"myfile.txt"
,
"UTF-8"
)
val
iter
=
source
.
buffered
while
(
iter
.
hasNext
)
{
if
(
iter
.
head
==
someChar
)
deal
with
iter
.
next
else
.
.
.
}
source
.
close
(
)
|
读取词法单元和数字
1
2
3
4
5
6
|
// 一个快而脏的方式
val
tokens
=
source
.
mkString
.
sqlit
(
"\\s+"
)
// 根据正则读取词法单元
// 转换成数字
val
numbers
=
for
(
w
<
-
tokens
)
yield
w
.
toDouble
val
numbers
=
tokens
.
map
(
_
.
toDouble
)
|
还可以使用java.util.Scanner类来读取文件中的文本和数字。
从非文件源读取
1
2
3
4
|
// 从URL读取,需要注意字符编码
val
source1
=
Source
.
fromURL
(
"http://horstmann.com"
,
"UTF-8"
)
val
source2
=
Source
.
fromString
(
"Hello, world!"
)
// 从指定的字符串读取,调试时很有用
val
source3
=
Source
.
stdin
// 从标准输入读取
|
读取二进制文件
Scala没有提供读取二进制文件的方法,需要使用Java类库。
1
2
3
4
5
|
val
file
=
new
File
(
filename
)
val
in
=
new
FileInputStream
(
file
)
val
bytes
=
new
Array
[
Byte
]
(
file
.
length
.
toInt
)
in
.
read
(
bytes
)
in
.
close
(
)
|
写入文本文件
Scala也没有对写入文件的内建支持,依旧可以使用Java类库来实现。如:
1
2
3
|
val
out
=
new
PrintWriter
(
"numbers.txt"
)
for
(
i
<
-
1
to
100
)
out
.
println
(
i
)
out
.
close
(
)
|
但是在使用这里printf方法时,传递AnyVal(比如各种数字)类型给方法时,编译器会要求将其转换成AnyRef:
1
2
3
4
5
|
out
.
printf
(
"%6d %10.2f"
,
quantity
.
asInstanceOf
[
AnyRef
]
,
price
.
asInstanceOf
[
AnyRef
]
)
// 为了避免这个麻烦,可以使用String类的format方法
out
.
printf
(
"%6d %10.2f"
.
format
(
quantity
,
price
)
)
|
访问目录
目前没有“正式的”用来访问目录中所有文件,或递归遍历所有目录的类。(本章各种没有内建没有正式是闹哪样啊!也无所谓了,用Java类库就是了。)下面探讨一下替代方案。
遍历某目录下所有子目录的函数:
1
2
3
4
5
|
import
java
.
io
.
File
def
subdirs
(
dir
:
File
)
:
Iterator
[
File
]
=
{
val
children
=
dir
.
listFiles
.
filter
(
_
.
isDirectory
)
children
.
toIterator
++
children
.
toIterator
.
flatMap
(
subdirs
_
)
}
|
不是很好懂,可能需要查阅API文档来帮忙。
如果使用了Java 7,可以使用java.nio.file.Files类的walkFileTree方法。该类用到了FileVisitor接口。
这里提到了Java的nio包,让我记起来的确需要去学一学了,io包大概已经真满足不了现在的需求了。
在Scala中通常更偏好用函数对象来指定工作内容,而不是接口(但在本例中接口可以有更细粒度的控制)。
以下隐式转换让函数可以与接口相匹配:
1
2
3
4
5
6
7
8
9
10
|
import
java
.
nio
.
file
.
_
implicit
def
makeFileVisitor
(
f
:
(
Paht
)
=
>
Unit
)
=
new
SimpleFileVisitor
[
Path
]
{
override
def
visitFile
(
p
:
Path
,
attrs
:
attribute
.
BasicFileAttributes
)
=
{
f
(
p
)
FileVisitResult
.
CONTINUE
}
}
// 调用打印出所有的子目录
Files
.
walkFileTree
(
dir
.
toPaht
,
(
f
:
Path
)
=
>
println
(
f
)
)
|
这个真心有点超出太多了,放着先吧,等学了相关的知识再回来看。
序列化
使用序列化来将对象传输,或者是临时存储,序列化并不适合长期存储,会因为类的更新而出现问题。
在Scala中的序列化类:
1
|
@SerialVersionUID
(
42L
)
class
Person
extends
Serializable
|
如果可以接受缺省的UID,可以不要@SerialVersionUID注解。
序列化和反序列化:
1
2
3
4
5
6
7
8
9
|
val
fred
=
new
Person
(
.
.
.
)
import
java
.
io
.
_
val
out
=
new
ObjectOutputStream
(
new
FileOutputStream
(
"/tmp/test.obj"
)
)
out
.
writeObject
(
fred
)
out
.
close
(
)
val
in
=
new
ObjectInputStream
(
new
FileInputStream
(
"/tmp/test.obj"
)
)
val
savedFred
=
in
.
readObject
(
)
.
asInstanceOf
[
Person
]
in
.
close
(
)
|
进程控制(A2)
Scala设计目标之一是能在简单的脚本话任务和大型程序之间保持良好的伸缩性。
scala.sys.process包提供了用于与shell程序交互的工具。这样一来,就可以使用Scala编写shell脚本。(虽然我用了Linux不少时间了,但我还是不会bash编程啊!怎么会有这么多东西需要学啊!~来不及的感觉…)
简单例子:
1
2
|
import
sys
.
process
.
_
"ls -al .."!
|
ls -al ..命令将会被执行,然后将结果在标准输出中显示。
sys.process包内有从字符串到ProcessBuilder对象的隐式转换。!操作符执行的就是这个ProcessBuilder对象。返回的结果是被执行程序的返回值:成功执行是0;否则是显示错误的非0值。
如果使用!!操作符,输出会以字符串的形式返回:
1
|
val
result
=
"ls -al .."
!
!
|
还可以使用管道(不了解什么是管道的,请先学习一下Linux基础),使用#!操作符。
1
2
3
4
5
6
7
8
|
// 管道,使用#|
"ls -al .."
#
|
"grep sec"
!
// 重定向到文件,使用#>
"ls -al .."
#
>
new
File
(
"output.txt"
)
!
// 追加到文件,使用#>>
"ls -sl .."
#
>>
new
File
(
"output.txt"
)
!
// 将文件内容作为输入,使用#<
"grep sec"
#
<
new
File
(
"output.txt"
)
!
// 也可以使用URL
|
另外还可以使用#||和#&&来控制进程。不过这种功能还是直接使用Scala来实现更好。
可以自己指定执行进程的目录和环境变量;设置环境变量用的是一系列的对偶。
1
2
|
val
p
=
Process
(
cmd
,
new
File
(
dirName
)
,
(
"LANG"
,
"en_US"
)
)
"echo 42"
#
|
p
!
// 执行
|
个人认为这个功能还是很强大的,可以使用现有的命令来做到大量的事情。不过是否会带来安全上的影响,以及可能会使程序变得与平台有关,还是要慎重使用。
正则表达式
与正则表达式相关的类是scala.util.matching.Regex类。要构造一个Regex对象,使用String类的r方法即可。如果正则表达式中包含反斜杠或引号之类的需要转义的字符,那么最好是使用原始(raw)字符串,以三个”号包围。
1
2
|
val
numPattern
=
"[0-9]+"
.
r
val
wsnumwsPattern
=
""
"\s+[0-9]+\s+"
""
.
r
|
1
2
3
4
5
6
7
|
// findAllIn方法返回遍历所有匹配项的迭代器
for
(
matchString
<
-
numPattern
.
findAllIn
(
"99 bottles, 98 bottles"
)
)
// 找到首个匹配项
val
m1
=
wsnumwsPattern
.
findFirstIn
(
"99 bottles, 98 bottles"
)
// 返回Some(" 98 ")
// findFirstIn方法返回Option[String]
|
用findPrefixOf方法检查某个字符串的开始部分是否能够匹配。
可以用相应的replace方法来替换掉匹配的部分。
正则表达式组
分组使得获取正则表达式的子表达式更加方便。在想要提取的子表达式两侧加上圆括号:
1
|
val
numitemPattern
=
"([0-9]+) ([a-z]+"
.
r
|
匹配组将正则表达式对象当做“提取器”来使用:
1
2
|
val
numitemPattern
(
num
,
item
)
=
"99 bottles"
// num被赋值为"99",item被赋值为"bottles"
|