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

文件和正则表达式html

摘要:java

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

1. Source.fromFile(...).getLines.toArray输出文件的全部行正则表达式

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

3. 将字符串转换为数字,能够用tolnt或toDouble方法编程

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

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

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

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】。

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

相关文章
相关标签/搜索