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

本文版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,不然保留追究法律責任的權利

相關文章
相關標籤/搜索