1. 程式人生 > >《Groovy官方文件》Groovy開發套件-使用IO

《Groovy官方文件》Groovy開發套件-使用IO

Groovy開發套件 第一部分

1 I/O 的使用

Groovy提供了豐富的方法來操作IO流。當然你也可以使用標準的Java程式碼來進行這些操作。但是Groovy提供了更多方便的方式來操作檔案,流…

你可以先看看下面列舉的一些方法:

下面的一些小節將提供一些示例來演示如何使用這些類,如果你想檢視所有方法的詳細用法,請閱讀GDK的介面文件

1.1 讀檔案

作為開篇的第一個示例,我們來看看如何使用Groovy來讀檔案並且列印讀到的所有行:

new File(baseDir, 'haiku.txt').eachLine { line ->
println line
}

Groovy的eachLine方法是File類自動載入並且可有有許多的變體,比如如果你想知道行號,你可以使用以下的變體:

new File(baseDir, 'haiku.txt').eachLine { line, nb ->
println "Line $nb: $line"
}

在eachLine方法體裡,丟擲任何異常後該方法都可以確保正常將檔案流關閉。Groovy的其他操作檔案流的方法也提供了該特性。
比如說,有些場景你可能更喜歡用Reader,該類的檔案操作方法依然可以自動管理檔案流。在下面的這個例子裡,即便操作檔案過程中丟擲了異常,檔案流依然可以正常關閉:

def count = 0, MAXSIZE = 3
new File(baseDir,"haiku.txt").withReader { reader ->
while (reader.readLine()) {
if (++count > MAXSIZE) {
throw new RuntimeException('Haiku should only have 3 verses')
}
}
}

如果你想將一個文字檔案中的所有行放到一個list裡,你可以這樣寫:

def list = new File(baseDir, 'haiku.txt').collect {it}

你甚至還可以使用as方法將一個檔案內容放到一個數組中:

def array = new File(baseDir, 'haiku.txt') as String[]

很多時候你想將一個檔案的內容放到一個byte陣列,你覺得需要多少程式碼來實現呢?Groovy將這個操作變成了一行程式碼:
byte[] contents = file.bytes
使用IO並不僅僅是操作檔案,事實上,更多的時候你需要操作輸入/輸出流,這就是為什麼Groovy提供了非常豐富的方法來實現這一需求,你可以參見這個文件:

InputStream
舉個例子,你可以非常容易地從一個檔案中獲取一個輸入流:

def is = new File(baseDir,'haiku.txt').newInputStream()
// do something ...
is.close()

但是這個方式需要你手動關閉輸入流,事實上Groovy還提供了一種更加通用和快捷的方式,那就是使用withInputStream來操作:

new File(baseDir,'haiku.txt').withInputStream { stream ->
// do something ...
}

1.2 寫檔案

有時候你並不是想讀檔案而是寫檔案。這時一種方式是使用Writer:

new File(baseDir,'haiku.txt').withWriter('utf-8') { writer ->
writer.writeLine 'Into the ancient pond'
writer.writeLine 'A frog jumps'
writer.writeLine 'Water’s sound!'
}

事實上對於上面這個簡單的例子,使用 <<操作符就綽綽有餘了:

new File(baseDir,'haiku.txt') << '''Into the ancient pond
A frog jumps
Water’s sound!'''

當然,我們並不是僅僅處理文字內容,但你也可以使用Writer或直接寫位元組:
file.bytes = [66,22,11]
當然你也可以直接處理輸出流,比如說下面的例子演示瞭如何建立一個輸出流並寫入到一個檔案:

def os = new File(baseDir,'data.bin').newOutputStream()
// do something ...
os.close()

但是這個例子要求你手動關閉輸出流。一種更好的做法是使用withOutputStream,任何時候只要丟擲了異常它都會關閉流:

new File(baseDir,'data.bin').withOutputStream { stream ->
// do something ...
}

1.3 遍歷檔案樹

在指令碼上下文裡,一種很常見的場景是遍歷檔案樹來找到特定的檔案進行特定的處理。Groovy提供了多種方法來做這個事情。比如說可以操作某個目錄下的全部檔案:

dir.eachFile { file ->
println file.name
}                         //(1)
dir.eachFileMatch(~/.*\.txt/) { file ->
println file.name
}                         //(2)

  1. 列舉給定目錄下的每個檔案
  2. 在給定目錄下查詢匹配格式的檔案

你經常需要處理更深層次的目錄,可以使用 eachFileRecurse:

dir.eachFileRecurse { file ->
println file.name
}                               //(1)

dir.eachFileRecurse(FileType.FILES) { file ->
println file.name
}                               //(2)

  1. 遞迴列舉所有檔案和目錄
  2. 僅僅遞迴列舉檔案

需要更加複雜的遍歷技術,可以使用 traverse方法,需要你設定一個特定的遞迴標識來終止遞迴:

dir.traverse { file ->
if (file.directory && file.name=='bin') {
FileVisitResult.TERMINATE                   //(1)
} else {
println file.name
FileVisitResult.CONTINUE                   //(2)
}
}

  1. 如果當前檔案是一個目錄並且名字是bin,停止遍歷
  2. 列印檔名並繼續

1.4 資料和物件

在Java裡,使用java.io.DataOutputStream 和 java.io.DataInputStream類來序列化和反序列化資料是非常常見的。Groovy裡,這步操作將變得更加容易,比如,你可以序列化資料到一個檔案然後使用下面的程式碼反序列:

boolean b = true
String message = 'Hello from Groovy'
// Serialize data into a file
file.withDataOutputStream { out ->
out.writeBoolean(b)
out.writeUTF(message)
}
// ...
// Then read it back
file.withDataInputStream { input ->
assert input.readBoolean() == b
assert input.readUTF() == message
}

類似地,如果你想要序列化的資料實現了Serializable介面,你可以使用一個物件輸出流來處理,如下面的示例所示:

Person p = new Person(name:'Bob', age:76)
// Serialize data into a file
file.withObjectOutputStream { out ->
out.writeObject(p)
}
// ...
// Then read it back
file.withObjectInputStream { input ->
def p2 = input.readObject()
assert p2.name == p.name
assert p2.age == p.age
}

1.5 執行外部程序

前面的章節描述了使用Groovy來處理檔案,Readers或流是一件很簡單的是。但是在一些領域向系統管理員或開發經常需要和外部程序進行互動。
Groovy提供了一種簡單的方式來執行命令列程序。僅僅需要將命令列寫成字串然後呼叫execute方法。舉個例子,子啊一個*nix機器上(或者一臺安裝了*nix命令執行環境的windows機器上)你可以執行下面的程式碼:

def process = "ls -l".execute()             (1)
println "Found text ${process.text}"       (2)

  1. 在外部程序執行ls命令
  2. 處理輸出並且返回文字

Execute方法返回一個java.lang.Process例項,可以使用in/out/err流來處理,通過返回值可以檢視處理情況。
eg:這裡有一個和上面命令類似但是現在是每次處理一個結果流:

def process = "ls -l".execute()             (1)
process.in.eachLine { line ->               (2)
println line                           (3)
}

  1. 在外部程序執行ls命令
  2. 對每個輸入流進行處理
  3. 列印line的內容

in 相當於標準輸出命令中的輸入流, out指代你傳送到程序(標準輸入流)中的資料的流。
記住,對於內建的shell命令需要有特殊的處理,因此如果你想在一臺windows機器上列一個某個目錄的所有檔案可以這樣寫:

def process = "dir".execute()
println "${process.text}"

當出現 Cannot run program “dir”: CreateProcess error=2, The system cannot find the file specified. 時你將收到一個IOException
這是因為dir命令是windows shell(cmd.exe)內建的命令。不能僅僅是執行dir,你應該這樣寫:

def process = "cmd /c dir".execute()
println "${process.text}"

同樣,因為這個功能使用了java.lang.Process 類,這個類的一些缺陷就必須考慮進去,javadoc關於這個類是這樣說的:

因為一些原生平臺僅僅提供受限緩衝區大小的標準輸入輸出流,因此對於失敗的寫輸入流操作或讀輸出流操作可能會造成程序阻塞甚至死鎖。

因為這一點,Groovy 提供了一個額外的幫助方法類使得流處理起來更加方便。
下面的例子是如何無阻塞處理素有的輸出(包括錯誤流輸出):

def p = "rm -f foo.tmp".execute([], tmpDir)
p.consumeProcessOutput()
p.waitFor()

consumeProcessOutput 也有一些變體來使用StringBuffer,InputStream,OutputStream等等,完整的例子可以參考GDK API for java.lang.Process
除此之外,也有一個pipeTo命令(對應 | (管道))使得輸出流可以承接到另外一個程序的輸入流。
這有一些示例的使用:

proc1 = 'ls'.execute()
proc2 = 'tr -d o'.execute()
proc3 = 'tr -d e'.execute()
proc4 = 'tr -d i'.execute()
proc1 | proc2 | proc3 | proc4
proc4.waitFor()
if (proc4.exitValue()) {
println proc4.err.text
} else {
println proc4.text
}

處理錯誤:

def sout = new StringBuilder()
def serr = new StringBuilder()
proc2 = 'tr -d o'.execute()
proc3 = 'tr -d e'.execute()
proc4 = 'tr -d i'.execute()
proc4.consumeProcessOutput(sout, serr)
proc2 | proc3 | proc4
[proc2, proc3].each { it.consumeProcessErrorStream(serr) }
proc2.withWriter { writer ->
writer << 'testfile.groovy'
}
proc4.waitForOrKill(1000)
println "Standard output: $sout"
println "Standard error: $serr"