如何在 Golang 讀取/寫入檔案

這篇是在 stackoverflow 上看到的

覺得滿實用

在Golang要操作檔案有三種方法

第一種就是操作檔案的指標

跟php的fopen差不多

以下是第一段程式碼

package main

import (
    "io"
    "os"
)

func main() {
    // open input file
    fi, err := os.Open("input.txt")
    if err != nil {
        panic(err)
    }
    // close fi on exit and check for its returned error
    defer func() {
        if err := fi.Close(); err != nil {
            panic(err)
        }
    }()

    // open output file
    fo, err := os.Create("output.txt")
    if err != nil {
        panic(err)
    }
    // close fo on exit and check for its returned error
    defer func() {
        if err := fo.Close(); err != nil {
            panic(err)
        }
    }()

    // make a buffer to keep chunks that are read
    buf := make([]byte, 1024)
    for {
        // read a chunk
        n, err := fi.Read(buf)
        if err != nil && err != io.EOF {
            panic(err)
        }
        if n == 0 {
            break
        }

        // write a chunk
        if _, err := fo.Write(buf[:n]); err != nil {
            panic(err)
        }
    }
}

第10行使用os.Open來開啟一個檔案

並返回該檔案的指標與錯誤訊息

每次開啟的檔案都要記得關閉

Golang有提供defer這個關鍵字

可以在最後執行某個function

而第22行使用os.Create來開啟一個檔案

跟os.Open一樣都是返回檔案的指標與錯誤訊息

這兩者有什麼差異呢?

其實os.Create跟os.Open都是呼叫同一個function

叫os.OpenFile(name string, flag int, perm FileMode) (*File, error)

差別就在於flag跟perm的參數不同

os.Open是單純的Read only

package main

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

func main() {
    // open input file
    fi, err := os.Open("input.txt")
    if err != nil {
        panic(err)
    }
    // close fi on exit and check for its returned error
    defer func() {
        if err := fi.Close(); err != nil {
            panic(err)
        }
    }()
    // make a read buffer
    r := bufio.NewReader(fi)

    // open output file
    fo, err := os.Create("output.txt")
    if err != nil {
        panic(err)
    }
    // close fo on exit and check for its returned error
    defer func() {
        if err := fo.Close(); err != nil {
            panic(err)
        }
    }()
    // make a write buffer
    w := bufio.NewWriter(fo)

    // make a buffer to keep chunks that are read
    buf := make([]byte, 1024)
    for {
        // read a chunk
        n, err := r.Read(buf)
        if err != nil && err != io.EOF {
            panic(err)
        }
        if n == 0 {
            break
        }

        // write a chunk
        if _, err := w.Write(buf[:n]); err != nil {
            panic(err)
        }
    }

    if err = w.Flush(); err != nil {
        panic(err)
    }
}

乍看跟第一種好像差不多

但是bufio有提供更多方法來讀取/寫入檔案

像是他可以讀取一整行bufio.ScanLines

或是其他好用方法可參考文件

第三種程式如下

package main

import (
    "io/ioutil"
)

func main() {
    // read the whole file at once
    b, err := ioutil.ReadFile("input.txt")
    if err != nil {
        panic(err)
    }

    // write the whole body at once
    err = ioutil.WriteFile("output.txt", b, 0644)
    if err != nil {
        panic(err)
    }
}

這種就非常簡短

適合新手使用

總結

我個人是使用第一種方式

因為我需要取得檔案指標來做其他事

像是產生這個檔案的MD5

不過要注意一點

若要重複使用檔案指標

就必須重置指標的位置

例如想取得檔案內容又想產生MD5

在取得內容之後

檔案指標是在檔案的結尾

所以要呼叫 f.Seek(0, 0)

來將指標移回開頭的位置

 

看更多