Go語言檔案讀寫

2020-07-16 10:05:17
本節將通過範例來為大家介紹Go語言中檔案讀寫的相關操作。

讀檔案

在Go語言中,檔案是使用指向 os.File 型別的指標來表示的,也叫做檔案控制代碼。在前面章節使用到過標準輸入 os.Stdin 和標準輸出 os.Stdout 都是 *os.File 型別的。讓我們來看看下面這個程式:
package main

import (
    "bufio"
    "fmt"
    "io"
    "os"
)

func main() {
    inputFile, inputError := os.Open("cookies.dat")
    if inputError != nil {
        fmt.Printf("開啟檔案時出錯", inputError.Error())
        return // 退出函數
    }
    defer inputFile.Close()
    inputReader := bufio.NewReader(inputFile)
    i := 0
    for {
        inputString, readerError := inputReader.ReadString('n')
        if readerError == io.EOF {
            return
        }
        i++
        fmt.Printf("第 %v 行:%s", i, inputString)
    }
}
變數 inputFile 是 *os.File 型別的。該型別是一個結構,表示一個開啟檔案的描述符(檔案控制代碼)。然後,使用 os 包裡的 Open 函數來開啟一個檔案。該函數的引數是檔名,型別為 string。在上面的程式中,我們以唯讀模式開啟 cookies.dat 檔案。

如果檔案不存在或者程式沒有足夠的許可權開啟這個檔案,Open 函數會返回一個錯誤:

inputFile, inputError = os.Open("cookies.dat")

如果檔案開啟正常,我們就使用 defer.Close() 語句確保在程式退出前關閉該檔案。然後,我們使用 bufio.NewReader 來獲得一個讀取器變數。

通過使用 bufio 包提供的讀取器(寫入器也類似),如上面程式所示,我們可以很方便的操作相對高層的 string 物件,而避免了去操作比較底層的位元組。

接著,我們在一個無限迴圈中使用 ReadString('n') 或 ReadBytes('n') 將檔案的內容逐行(行結束符 'n')讀取出來。

注意:在之前的例子中,我們看到,Unix 和 Linux 的行結束符是 n,而 Windows 的行結束符是 rn。在使用 ReadString 和 ReadBytes 方法的時候,我們不需要關心作業系統的型別,直接使用 n 就可以了。另外,我們也可以使用 ReadLine() 方法來實現相同的功能。

一旦讀取到檔案末尾,變數 readerError 的值將變成非空(事實上,常數 io.EOF 的值是 true),我們就會執行 return 語句從而退出迴圈。

其他類似函數:

1) 將整個檔案的內容讀到一個字串裡

如果想將整個檔案的內容讀到一個字串裡,可以使用 io/ioutil 包裡的 ioutil.ReadFile() 方法,該方法第一個返回值的型別是 []byte ,裡面存放讀取到的內容,第二個返回值是錯誤,如果沒有錯誤發生,第二個返回值為 nil。

【範例 1】使用函數 WriteFile() 將 []byte 的值寫入檔案。
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: %sn", err)
        // panic(err.Error())
    }
    fmt.Printf("%sn", string(buf))
    err = ioutil.WriteFile(outputFile, buf, 0x644)
    if err != nil {
        panic(err. Error())
    }
}

2) 帶緩衝的讀取

在很多情況下,檔案的內容是不按行劃分的,或者乾脆就是一個二進位制檔案。在這種情況下,ReadString() 就無法使用了,我們可以使用 bufio.Reader 的 Read() ,它只接收一個引數:
buf := make([]byte, 1024)
...
n, err := inputReader.Read(buf)
if (n == 0) { break}
變數 n 的值表示讀取到的位元組數.

3) 按列讀取檔案中的資料

如果資料是按列排列並用空格分隔的,你可以使用 fmt 包提供的以 FScan 開頭的一系列函數來讀取他們。

【範例 2】將 3 列的資料分別讀入變數 v1、v2 和 v3 內,然後分別把他們新增到切片的尾部。
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)
}
輸出結果:

[ABC FUNC GO]
[40 56 45]
[150 280 356]

注意:path 包裡包含一個子包叫 filepath ,這個子包提供了跨平台的函數,用於處理檔名和路徑。例如 Base() 函數用於獲得路徑中的最後一個元素(不包含後面的分隔符):

import "path/filepath"
filename := filepath.Base(path)

compress 包:讀取壓縮檔案

compress 包提供了讀取壓縮檔案的功能,支援的壓縮檔案格式為:bzip2、flate、gzip、lzw 和 zlib。

【範例 3】使用 Go語言讀取一個 gzip 檔案。
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: %sn", 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)
    }
}

寫檔案

請看以下程式:
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 creationn")
        return
    }
    defer outputFile.Close()
    outputWriter := bufio.NewWriter(outputFile)
    outputString := "hello world!n"
    for i:=0; i<10; i++ {
        outputWriter.WriteString(outputString)
    }
    outputWriter.Flush()
}
除了檔案控制代碼,我們還需要 bufio 的寫入器。我們以唯讀模式開啟檔案 output.dat ,如果檔案不存在則自動建立:

outputFile, outputError := os.OpenFile(“output.dat”, os.O_WRONLY|os.O_ CREATE, 0666)

可以看到,OpenFile 函數有三個引數:檔名、一個或多個標誌(使用邏輯運算子“|”連線),使用的檔案許可權。

我們通常會用到以下標誌:
  • os.O_RDONLY:唯讀
  • os.WRONLY:只寫
  • os.O_CREATE:建立:如果指定檔案不存在,就建立該檔案。
  • os.O_TRUNC:截斷:如果指定檔案已存在,就將該檔案的長度截為 0。

在讀檔案的時候,檔案的許可權是被忽略的,所以在使用 OpenFile 時傳入的第三個引數可以用 0。而在寫檔案時,不管是 Unix 還是 Windows,都需要使用 0666。

然後,我們建立一個寫入器(緩衝區)物件:

outputWriter := bufio.NewWriter(outputFile)

接著,使用一個 for 迴圈,將字串寫入緩衝區,寫 10 次:

outputWriter.WriteString(outputString)

緩衝區的內容緊接著被完全寫入檔案:outputWriter.Flush()

如果寫入的東西很簡單,我們可以使用fmt.Fprintf(outputFile, “Some test data.n”)直接將內容寫入檔案。fmt 包裡的 F 開頭的 Print 函數可以直接寫入任何 io.Writer,包括檔案。

【範例 4】不使用 fmt.FPrintf 函數,使用其他函數如何寫檔案:
package main
import "os"
func main() {
    os.Stdout.WriteString("hello, worldn")
    f, _ := os.OpenFile("test", os.O_CREATE|os.O_WRONLY, 0)
    defer f.Close()
    f.WriteString("hello, world in a filen")
}
使用os.Stdout.WriteString("hello, worldn"),我們可以輸出到螢幕。以只寫模式建立或開啟檔案“test”,並且忽略了可能發生的錯誤:

f, _ := os.OpenFile(“test”, os.O_CREATE|os.O_WRONLY, 0)

不使用緩衝區,直接將內容寫入檔案:f.WriteString()