Scala学习(九)---文件和正则表达式

文件和正则表达式

摘要:

在本篇中,你将学习如何执行常用的文件处理任务,比如从文件中读取所有行或单词,或者读取包含数字的文件等。本篇的要点包括:

1. Source.fromFile(...).getLines.toArray输出文件的所有行

2. Source.fromFile(...).mkString以字符串形式输出文件内容

3. 将字符串转换为数字,可以用tolnt或toDouble方法

4. 使用java的PrintWriter来写入文本文件

5. "正则",r是一个Regex对象

6. 如果你的正则表达式包含反斜杠或引号的话,用"""…"""

7. 如果正则模式包含分组,你可以用如下语法来提取它们的内容for (regex(变量1,变量n) <- 字符串)

读取行

读取文件中的所有行,可以调用scala.io.Source对象的getLines方法:

import scala.io.Source

val source=Source.fromFile ("myfile.txt", "UTF-8")

第一个参数可以是字符串或者是java.io.File,如果你知道文件使用的是当前平台缺省的字符编码,则可以略去第二个字符编码参数

val linelterator = source.getLines

结果是一个迭代器。你可以用它来逐条处理这些行:

for (l <- linelterator ) 处理l

或者你也可以对迭代器应用toArraytoBuffer方法,将这些行放到数组或数组缓冲当中:

val lines=source.getLines.toArray

有时候,你只想把整个文件读取成一个字符串。那更简单了:

val contents=source.mkString

需要注意的是,在用完Source的对象后,记得调用close

读取字符

要从文件中读取单个字符,你可以直接把Source对象当做迭代器,因为Source类扩展自Iterator[Char]:

for (c <- source) 处理c

如果你想查看某个字符但又不处理掉它的话,调用source对象的buffered方法。这样你就可以用head方法查看下一个字符,但同时并不把它当做是已处理的字符

val source = Source.fromFile ( "myfile.txt ", "UTF-8 ")

val iter=source.buffered

while ( iter.hasNext ) {

if ( iter.head是符合预期的 )

处理iter.next

else

…….

}

Source .close()

或者,如果你的文件不是很大,你也可以把它读取成一个字符串进行处理:

val contents = source.mkString

读取词法单元和数字

这里有一个快而脏的方式来读取源文件中所有以空格隔开词法单元

val tokens = source.mkString.split("\S+")

而要把字符串转换成数字,可以用tolnt或toDouble方法。举例来说,如果你有一个包含了浮点数的文件,则可以将它们统统读取到数组中:

val numbers=for (w <- tokens) yield w.toDouble

或者

val numbers = tokens.map(_.toDouble)

需要注意的是,我们总是可以使用java.util.Scanner类来处理同时包含文本和数字的文件。与此同时,你也可以从控制台读取数字:

print (" How old are you" ) // 缺省情况下系统会自动引入Console,并不需要对print和readlnt使用限定词

val age=readlnt()

注意:这些方法假定下一行输入包含单个数字,且前后都没有空格。否则会报NumberFormatException

从URL或其他源读取

Source对象有读取非文件源的方法:

val source1 = Source.fromURL("http://horstamnn.com", "UTF-8")

val source2 = Source.fromString( "Hello, World! " ) // 从给定的字符串读取,这对调试很有用

val source3 = Source.stdin //从标准输入读取

当你从URL读取时,你需要事先知道字符集,可能是通过HTTP头获取。更多信息参见www.w3.org/lnternational/O-charset

读取二进制文件

Scala并没有提供读取二进制文件的方法。你需要使用Java类库。以下是如何将文件读取成字节数组

val file = new File (filename)

val in = new FileInputStream(file)

val bytes = new Array[Byte](file.length.tolnt)

in.read (bytes)

in.close()

写入文本文件

Scala没有内建的对写入文件的支持。要写入文本文件,可使用java.io.PrintWriter,例如:

val out = new PrintWriter("numbers.txt")

for ( i <- 1 to 100 ) out.println(i)

out.close()

所有的逻辑都像我们预期的那样,除了printf方法外。当你传递数字给printf时,编译器会抱怨说你需要将它转换成AnyRef:

out.printf ( "%6d %10.2f",quantity.aslnstanceOf [AnyRef], price.aslnstanceOf[AnyRef] )

为了避免这个麻烦,你也可以用string类的format方法:

out.print( "%6d %10.2f". format (quantity, price))

需要注意的是:Console类的printf没有这个问题,你可以用来输出消息到控制台。

printf("%6d %10.2f",quantity, price)

访问目录

自定义处理

目前Scala并没有"正式的"用来访问某个目录中的所有文件,或者递归地遍历所有目录的类。下面,我们将探讨一些替代方案。编写产出遍历某目录下所有子目录的函数并不复杂:

import java.io.File

def subdirs (dir: File):Iterator[File] = {

val children = dir.listFiles.filter(_.isDirectory)

children.tolterator ++ children.toIterator.flatMap( subdirs _ )

}

利用这个函数,你可以像这样访问所有的子目录:

for(d <- subdirs (dir))处理d

或者,如果你用的是Java 7,你也可以使用java.nio.file.Files类walkFileTree方法,该类用到了FileVisitor接口

函数对象处理

在Scala中,我们通常喜欢用函数对象指定工作内容,而不是接口。以下隐式转换让函数可以与接口相匹配:

import java.nio.file._

implicit def makeFileVisitor( f: (Path)=>Unit ) = new SimpleFileVisitor[Path] {

override def visitFile( p: Path, attrs: attribute.BasicFileAttributes ) = {

f(p)

FileVisitResult.CONTINUE

}

}

这样一来,你就可以通过以下调用来打印出所有的子目录了:

Files.walkFileTree( dir.toPath, (f:Path) => println(f) )

当然,如果你不仅仅是想要打印出这些文件,则也可以在传AwalkFileTree方法的函数中指定其他要执行的动作

序列化

在Java中,我们用序列化来将对象传输到其他虚拟机,或临时存储。对于长期存储而言,序列化可能会比较笨拙,因为随着类的演进更新,处理不同版本间的对象是很烦琐的一件事。以下是如何在Java和Scala中声明一个可被序列化的类。

Java

public class Person implements java.io.Serializable {

private static final long serialVersionUID=42L;

}

Scala

@SerialVersionUID(42L) class Person extends Serializable

Serializable特质定义在scala包,因此不需要显式引入。如果你能接受缺省的ID,也可略去@SerialVersionUID注解。你可以按照常规的方式对对象进行序列化反序列化

val fred = new Person ()

import java.io._

val out = new ObjectOutputStream(new FileOutputStream ("/tmp/test.obj"))

out.writeObject (fred)

out.close()

val in = new ObjectlnputStream ( new FilelnputStream("/tmp/test.obj")

val savedFred=in.readObject() .aslnstanceOf[Person]

Scala集合类都是可序列化的,因此你可以把它们用做你的可序列化类的成员:

class Person extends Serializable{

private val friends = new ArrayBuffer[Person] // ArrayBuffer是可序列化的

}

进程控制

Scala脚本

按照传统习惯,程序员使用shell脚本来执行日常处理任务,比如把文件从一处移动到另一处,或者将一组文件拼接在一起。shell语言使得我们可以很容易地指定所需要的文件子集,以及将某个程序的输出以管道方式作为另一个程序的输入。话虽如此,从编程语言的角度看,大多数shell语言并不是那么完美。

Scala的设计目标之一就是能在简单的脚本化任务大型程序之间保持良好的伸缩性。scala.sys.process包提供了用于与shell程序交互的工具。你可以用Scala编写shell脚本,利用Scala提供的所有威力。如下是一个简单的示例:

import sys.process._

"ls -al .." !

这样做的结果是,Is -al ..命令被执行,显示上层目录的所有文件。执行结果被打印到标准输出。sys.process包包含了一个从字符串ProcessBuilder对象的隐式转换。!操作符执行的就是这个ProcessBuilder对象!操作符返回的结果是被执行程序的返回值:程序成功执行的话就是0,否则就是显示错误的非0值。

操作符和管道

如果你使用! !操作符而不是!操作符的话,输出会以字符串的形式返回

val result = "ls -al .." ! !

你还可以将一个程序的输出以管道形式作为输入传送到另一个程序,用}}I操作符:

"ls -al .." #| "grep sec" !

正如你看到的,进程类库使用的是底层操作系统的命令。在本例中,我用的是bash命令,因为bash在Linux、Mac OS X和Windows中都能找到

常用操作符

要把输出重定向到文件,使用撑#>操作符

"ls -al .." #> new File("output.txt") !

追加到文件末尾而不是从头覆盖的话,使用#>>操作符

"ls -al .." #>> new File ("output.txt") !

要把某个文件的内容作为输入,使用#<操作符

"grep sec" #< new File("output.txt") !

你还可以从URL重定向输入

"grep Scala" #< new URL( http://horstmann. com/index.html ) !

你可以将进程结合在一起使用,比如p#&&q:如果p成功,则执q;以及p#||q:如果p不成功,则执行q。由上可知,进程库使用人们熟悉的shell操作符I > >> < && ||,只不过给它们加上了#前缀,因此它们的优先级是相同的。

不同目录与环境变量运行进程

如果你需要在不同的目录下运行进程,或者使用不同的环境变量,用Process对象的apply方法来构造ProcessBuilder,给出命令起始目录,以及一串(名称,值)对偶来设置环境变量:

val p=Procass (cmd, new File (dirName), ("_LANG","nen US"))

然后用!操作符执行它:

"ech0 42" #I p !

正则表达式

当你在处理输入的时候,你经常会想要用正则表达式来分析它。scala.util.matching.Regex类让这件事情变得简单。要构造一个Regex对象,用String类的r方法即可:

val numPattern="[0-9]+ ".r

如果正则表达式包含反斜杠引号的话,那么最好使用"原始"字符串语法。例如:

val wsnumwsPattern = """s+[0-9]+s+""".r // 和"\s+[0-9]+\s+".r相比要更易读一些

findAllln方法返回遍历所有匹配项的迭代器。你可以在for循环中使用它:

for ( matchString <- numPattern.findAllln( "99 bottles, 98 bottles"))

处理matchString

或者将迭代器转成数组:

val matches = numPattern.findAllln("99 bottles, 98 bottles").toArray // Array(99, 98)

要找到字符串中的首个匹配项,可使用findFirstln。你得到的结果是一个Option[String]

val ml = wsnumwsPattern.findFirstln("99 bottles, 98 bottles") //Some("98")

要检查是否某个字符串的开始部分能匹配,可用findPrefixOf:

numPattern.findPreflxOf("99 bottlesf 98 bottles") //Some(99)

wSnumwsPattern.findPrefixOf("99 bottles, 98 bottles") // None

你可以替换首个匹配项,或全部匹配项:

numPattern.replaceFirstln("99 bottles, 98 bottles", "XX") // "XX bottles, 98 bottles"

numPattern. replaceAllIn("99 bottles, 98 bottles", "XX") // "XX bottles, XX bottles"

正则表达式组

分组可以让我们方便地获取正则表达式的子表达式。在你想要提取的子表达式两侧加上圆括号,例如:

val numitemPattern = " ([0-9]+) ([a-z]+) ".r

要匹配组,可以把正则表达式对象当做"提取器"使用,就像这样:

val numitemPattern (num, item) = "99 bottles" // 将num设为"99",item设为"bottles"

如果你想要从多个匹配项提取分组内容,可以像这样使用for语句:

for (numitemPattern (num,item) <- numitemPattern.findAllln("99 bottles, 98 bottles"))

处理num和item

如果,您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】。
如果,您希望更容易地发现我的新博客,不妨点击一下左下角的【关注我】。
如果,您对我的博客所讲述的内容有兴趣,请继续关注我的后续博客,我是【Sunddenly】。

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利

原文地址:https://www.cnblogs.com/sunddenly/p/4442408.html