文件和正則表達式 |
摘要:
在本篇中,你將學習如何執行常用的文件處理任務,比如從文件中讀取所有行或單詞,或者讀取包含數字的文件等。本篇的要點包括:
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
或者你也可以對迭代器應用toArray或toBuffer方法,將這些行放到數組或數組緩沖當中:
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】。本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利