在 Go 語(yǔ)言中,文件使用指向 os.File 類(lèi)型的指針來(lái)表示的,也叫做文件句柄。我們?cè)谇懊嬲鹿?jié)使用到過(guò)標(biāo)準(zhǔn)輸入 os.Stdin 和標(biāo)準(zhǔn)輸出 os.Stdout,他們的類(lèi)型都是 *os.File。讓我們來(lái)看看下面這個(gè)程序:
示例 12.4 fileinput.go:
package main
import (
"bufio"
"fmt"
"io"
"os"
)
func main() {
inputFile, inputError := os.Open("input.dat")
if inputError != nil {
fmt.Printf("An error occurred on opening the inputfile\n" +
"Does the file exist?\n" +
"Have you got acces to it?\n")
return // exit the function on error
}
defer inputFile.Close()
inputReader := bufio.NewReader(inputFile)
for {
inputString, readerError := inputReader.ReadString('\n')
fmt.Printf("The input was: %s", inputString)
if readerError == io.EOF {
return
}
}
}
變量 inputFile 是 *os.File 類(lèi)型的。該類(lèi)型是一個(gè)結(jié)構(gòu),表示一個(gè)打開(kāi)文件的描述符(文件句柄)。然后,使用 os 包里的 Open 函數(shù)來(lái)打開(kāi)一個(gè)文件。該函數(shù)的參數(shù)是文件名,類(lèi)型為 string。在上面的程序中,我們以只讀模式打開(kāi) input.dat 文件。
如果文件不存在或者程序沒(méi)有足夠的權(quán)限打開(kāi)這個(gè)文件,Open函數(shù)會(huì)返回一個(gè)錯(cuò)誤:inputFile, inputError = os.Open("input.dat")。如果文件打開(kāi)正常,我們就使用 defer inputFile.Close() 語(yǔ)句確保在程序退出前關(guān)閉該文件。然后,我們使用 bufio.NewReader 來(lái)獲得一個(gè)讀取器變量。
通過(guò)使用 bufio 包提供的讀取器(寫(xiě)入器也類(lèi)似),如上面程序所示,我們可以很方便的操作相對(duì)高層的 string 對(duì)象,而避免了去操作比較底層的字節(jié)。
接著,我們?cè)谝粋€(gè)無(wú)限循環(huán)中使用 ReadString('\n') 或 ReadBytes('\n') 將文件的內(nèi)容逐行(行結(jié)束符 '\n')讀取出來(lái)。
注意: 在之前的例子中,我們看到,Unix和Linux的行結(jié)束符是 \n,而Windows的行結(jié)束符是 \r\n。在使用 ReadString 和 ReadBytes 方法的時(shí)候,我們不需要關(guān)心操作系統(tǒng)的類(lèi)型,直接使用 \n 就可以了。另外,我們也可以使用 ReadLine() 方法來(lái)實(shí)現(xiàn)相同的功能。
一旦讀取到文件末尾,變量 readerError 的值將變成非空(事實(shí)上,常量 io.EOF 的值是 true),我們就會(huì)執(zhí)行 return 語(yǔ)句從而退出循環(huán)。
其他類(lèi)似函數(shù):
1) 將整個(gè)文件的內(nèi)容讀到一個(gè)字符串里:
如果您想這么做,可以使用 io/ioutil 包里的 ioutil.ReadFile() 方法,該方法第一個(gè)返回值的類(lèi)型是 []byte,里面存放讀取到的內(nèi)容,第二個(gè)返回值是錯(cuò)誤,如果沒(méi)有錯(cuò)誤發(fā)生,第二個(gè)返回值為 nil。請(qǐng)看示例 12.5。類(lèi)似的,函數(shù) WriteFile() 可以將 []byte 的值寫(xiě)入文件。
示例 12.5 read_write_file1.go:
package main
import (
"fmt"
"io/ioutil"
"os"
)
func main() {
inputFile := "products.txt"
outputFile := "products_copy.txt"
buf, err := ioutil.ReadFile(inputFile)
if err != nil {
fmt.Fprintf(os.Stderr, "File Error: %s\n", err)
// panic(err.Error())
}
fmt.Printf("%s\n", string(buf))
err = ioutil.WriteFile(outputFile, buf, 0644) // oct, not hex
if err != nil {
panic(err.Error())
}
}
2) 帶緩沖的讀取
在很多情況下,文件的內(nèi)容是不按行劃分的,或者干脆就是一個(gè)二進(jìn)制文件。在這種情況下,ReadString()就無(wú)法使用了,我們可以使用 bufio.Reader 的 Read(),它只接收一個(gè)參數(shù):
buf := make([]byte, 1024)
...
n, err := inputReader.Read(buf)
if (n == 0) { break}
變量 n 的值表示讀取到的字節(jié)數(shù).
3) 按列讀取文件中的數(shù)據(jù)
如果數(shù)據(jù)是按列排列并用空格分隔的,你可以使用 fmt 包提供的以 FScan 開(kāi)頭的一系列函數(shù)來(lái)讀取他們。請(qǐng)看以下程序,我們將 3 列的數(shù)據(jù)分別讀入變量 v1、v2 和 v3 內(nèi),然后分別把他們添加到切片的尾部。
示例 12.6 read_file2.go:
package main
import (
"fmt"
"os"
)
func main() {
file, err := os.Open("products2.txt")
if err != nil {
panic(err)
}
defer file.Close()
var col1, col2, col3 []string
for {
var v1, v2, v3 string
_, err := fmt.Fscanln(file, &v1, &v2, &v3)
// scans until newline
if err != nil {
break
}
col1 = append(col1, v1)
col2 = append(col2, v2)
col3 = append(col3, v3)
}
fmt.Println(col1)
fmt.Println(col2)
fmt.Println(col3)
}
輸出結(jié)果:
[ABC FUNC GO]
[40 56 45]
[150 280 356]
注意: path 包里包含一個(gè)子包叫 filepath,這個(gè)子包提供了跨平臺(tái)的函數(shù),用于處理文件名和路徑。例如 Base() 函數(shù)用于獲得路徑中的最后一個(gè)元素(不包含后面的分隔符):
import "path/filepath"
filename := filepath.Base(path)
練習(xí) 12.3:read_csv.go
文件 products.txt 的內(nèi)容如下:
"The ABC of Go";25.5;1500
"Functional Programming with Go";56;280
"Go for It";45.9;356
"The Go Way";55;500
每行的第一個(gè)字段為 title,第二個(gè)字段為 price,第三個(gè)字段為 quantity。內(nèi)容的格式基本與 示例 12.3c 的相同,除了分隔符改成了分號(hào)。請(qǐng)讀取出文件的內(nèi)容,創(chuàng)建一個(gè)結(jié)構(gòu)用于存取一行的數(shù)據(jù),然后使用結(jié)構(gòu)的切片,并把數(shù)據(jù)打印出來(lái)。
關(guān)于解析 CSV 文件,encoding/csv 包提供了相應(yīng)的功能。具體請(qǐng)參考 http://golang.org/pkg/encoding/csv/
compress包:讀取壓縮文件compress包提供了讀取壓縮文件的功能,支持的壓縮文件格式為:bzip2、flate、gzip、lzw 和 zlib。
下面的程序展示了如何讀取一個(gè) gzip 文件。
示例 12.7 gzipped.go:
package main
import (
"fmt"
"bufio"
"os"
"compress/gzip"
)
func main() {
fName := "MyFile.gz"
var r *bufio.Reader
fi, err := os.Open(fName)
if err != nil {
fmt.Fprintf(os.Stderr, "%v, Can't open %s: error: %s\n", os.Args[0], fName,
err)
os.Exit(1)
}
fz, err := gzip.NewReader(fi)
if err != nil {
r = bufio.NewReader(fi)
} else {
r = bufio.NewReader(fz)
}
for {
line, err := r.ReadString('\n')
if err != nil {
fmt.Println("Done reading file")
os.Exit(0)
}
fmt.Println(line)
}
}
請(qǐng)看以下程序:
示例 12.8 fileoutput.go:
package main
import (
"os"
"bufio"
"fmt"
)
func main () {
// var outputWriter *bufio.Writer
// var outputFile *os.File
// var outputError os.Error
// var outputString string
outputFile, outputError := os.OpenFile("output.dat", os.O_WRONLY|os.O_CREATE, 0666)
if outputError != nil {
fmt.Printf("An error occurred with file opening or creation\n")
return
}
defer outputFile.Close()
outputWriter := bufio.NewWriter(outputFile)
outputString := "hello world!\n"
for i:=0; i<10; i++ {
outputWriter.WriteString(outputString)
}
outputWriter.Flush()
}
除了文件句柄,我們還需要 bufio 的 Writer。我們以只寫(xiě)模式打開(kāi)文件 output.dat,如果文件不存在則自動(dòng)創(chuàng)建:
outputFile, outputError := os.OpenFile("output.dat", os.O_WRONLY|os.O_CREATE, 0666)
可以看到,OpenFile 函數(shù)有三個(gè)參數(shù):文件名、一個(gè)或多個(gè)標(biāo)志(使用邏輯運(yùn)算符“|”連接),使用的文件權(quán)限。
我們通常會(huì)用到以下標(biāo)志:
os.O_RDONLY:只讀 os.O_WRONLY:只寫(xiě) os.O_CREATE:創(chuàng)建:如果指定文件不存在,就創(chuàng)建該文件。 os.O_TRUNC:截?cái)啵喝绻付ㄎ募汛嬖?,就將該文件的長(zhǎng)度截為0。在讀文件的時(shí)候,文件的權(quán)限是被忽略的,所以在使用 OpenFile 時(shí)傳入的第三個(gè)參數(shù)可以用0。而在寫(xiě)文件時(shí),不管是 Unix 還是 Windows,都需要使用 0666。
然后,我們創(chuàng)建一個(gè)寫(xiě)入器(緩沖區(qū))對(duì)象:
outputWriter := bufio.NewWriter(outputFile)
接著,使用一個(gè) for 循環(huán),將字符串寫(xiě)入緩沖區(qū),寫(xiě) 10 次:outputWriter.WriteString(outputString)
緩沖區(qū)的內(nèi)容緊接著被完全寫(xiě)入文件:outputWriter.Flush()
如果寫(xiě)入的東西很簡(jiǎn)單,我們可以使用 fmt.Fprintf(outputFile, "Some test data.\n") 直接將內(nèi)容寫(xiě)入文件。fmt 包里的 F 開(kāi)頭的 Print 函數(shù)可以直接寫(xiě)入任何 io.Writer,包括文件(請(qǐng)參考章節(jié)12.8)。
程序 filewrite.go 展示了不使用 fmt.FPrintf 函數(shù),使用其他函數(shù)如何寫(xiě)文件:
示例 12.8 filewrite.go:
package main
import "os"
func main() {
os.Stdout.WriteString("hello, world\n")
f, _ := os.OpenFile("test", os.O_CREATE|os.O_WRONLY, 0666)
defer f.Close()
f.WriteString("hello, world in a file\n")
}
使用 os.Stdout.WriteString("hello, world\n"),我們可以輸出到屏幕。
我們以只寫(xiě)模式創(chuàng)建或打開(kāi)文件"test",并且忽略了可能發(fā)生的錯(cuò)誤:f, _ := os.OpenFile("test", os.O_CREATE|os.O_WRONLY, 0666)
我們不使用緩沖區(qū),直接將內(nèi)容寫(xiě)入文件:f.WriteString( )
練習(xí) 12.4:wiki_part1.go
(這是一個(gè)獨(dú)立的練習(xí),但是同時(shí)也是為章節(jié)15.4做準(zhǔn)備)
程序中的數(shù)據(jù)結(jié)構(gòu)如下,是一個(gè)包含以下字段的結(jié)構(gòu):
type Page struct {
Title string
Body []byte
}
請(qǐng)給這個(gè)結(jié)構(gòu)編寫(xiě)一個(gè) save 方法,將 Title 作為文件名、Body作為文件內(nèi)容,寫(xiě)入到文本文件中。
再編寫(xiě)一個(gè) load 函數(shù),接收的參數(shù)是字符串 title,該函數(shù)讀取出與 title 對(duì)應(yīng)的文本文件。請(qǐng)使用 *Page 做為參數(shù),因?yàn)檫@個(gè)結(jié)構(gòu)可能相當(dāng)巨大,我們不想在內(nèi)存中拷貝它。請(qǐng)使用 ioutil 包里的函數(shù)(參考章節(jié)12.2.1)。