Golang 文件操作的那些事兒

Os模塊的使用與源碼研究

文件:計算機(jī)中的文件是存儲在外部介質(zhì)(通常是磁盤)上的數(shù)據(jù)集合,文件分為文本文件和二進(jìn)制文件。例如咱們常見的文件后綴名.exe,.txt,'.word'...等等

文件的基本操作可簡單分為、兩類,也就是咱們所說的CURD(增刪改查),也是基于此兩類操作??珊唵卫斫鉃?code>打開文件夾、CURD、關(guān)閉文件夾。結(jié)束~

golang對于文件基本上都是基于Golang的os模塊,那讓我們一起了解一下,那么Golang是如何對文件進(jìn)行操作呢。Let's Go~

打開文件

Golang中打開文件使用os.Open模塊,官方os.open部分源碼如下:

// os.Open
// Open opens the named file for reading. If successful, methods on
// the returned file can be used for reading; the associated file
// descriptor has mode O_RDONLY.
// If there is an error, it will be of type *PathError.
func Open(name string) (*File, error) {
    return OpenFile(name, O_RDONLY, 0)
}

Open打開命名文件以供讀取。如果成功,則可以使用返回文件上的方法進(jìn)行讀??;關(guān)聯(lián)的文件。描述符的模式為O_RDONLY。 如果有錯誤,它將是* PathError類型。

它接收一個string 類型的變量name,返回兩個值,F(xiàn)ile的指針和錯誤error。那么我們使用它打開文件的的時候就需要這樣做

fileObj, err := os.Open(name string)
// 其中os.Open中的name為路徑Path

基礎(chǔ)使用的介紹暫且為止,其實(shí)我們更應(yīng)該關(guān)心的應(yīng)該是OpenFile(name, O_RDONLY, 0),這個函數(shù)到底干了啥,我們追蹤一下這個函數(shù)(在GoLang編輯器中, mac可以直接使用command + 鼠標(biāo)左鍵直接進(jìn)入,Win可以使用ctrl + 鼠標(biāo)左鍵),如下:

func OpenFile(name string, flag int, perm FileMode) (*File, error) {
    testlog.Open(name)
    f, err := openFileNolog(name, flag, perm)
    if err != nil {
        return nil, err
    }
    f.appendMode = flag&O_APPEND != 0

    return f, nil
}
// OpenFile是廣義的open調(diào)用;大多數(shù)用戶將使用Open 或Create代替。它打開帶有指定標(biāo)志的命名文件(O_RDONLY等)。如果該文件不存在,并且傳遞了O_CREATE標(biāo)志,則會使用模式perm(在umask之前)創(chuàng)建該文件。如果成功,返回文件上的方法可以用于I / O。 如果有錯誤,它將是* PathError類型。

這個文件全部內(nèi)容還是有點(diǎn)分量的,有信息的伙伴,可以詳細(xì)的閱讀一下全部內(nèi)容。暫且為止

那讓我們實(shí)踐一下,使用Golang打開文件,如下

package main

import (
    "fmt"
    "os"
)

func main() {
    // 打開此文件,./main.go為相對路徑。在這里是此文件
    fileObj, err := os.Open("./main.go")
    // 異常處理
    if err != nil {
        fmt.Printf("Open File Error Message:%#v\n", err)
        return
    }
    // 嘗試打印(此處輸出的為地址值)
    fmt.Println(&fileObj)
    // defer 關(guān)閉文件
    defer fileObj.Close()
}

image

以防忘記關(guān)閉文件,造成bug,我們在這里使用defer + 關(guān)閉。

注意:在編輯器中并不建議直接使用鼠標(biāo)右鍵運(yùn)行,這樣可能會導(dǎo)致路徑錯誤。大部分的編輯器都并不是只運(yùn)行此文件!!!

Open File Error Message:&os.PathError{Op:"open", Path:"./main.go", Err:0x2}

如果你遇見了類似的錯誤,你可以直接在終端中,切換到當(dāng)前路徑。使用go run main.go,直接運(yùn)行。這樣就可以直接得到正確的結(jié)果啦

讀取文件

打開文件之后,那么我們可以就可以對他們進(jìn)行操作了,我們在這里主要演示一下讀取文件的操作。還是老樣子,先看一下主要的相關(guān)源碼,如下:

// FileObj.Read()
func (f *File) Read(b []byte) (n int, err error) {
    if err := f.checkValid("read"); err != nil {
        return 0, err
    }
    n, e := f.read(b)
    return n, f.wrapErr("read", e)
}

// f.read(b)
func (f *File) read(b []byte) (n int, err error) {
    n, err = f.pfd.Read(b)
    runtime.KeepAlive(f)
    return n, err
}

FileObj.Read()

示例化接受文件的地址值(也就是咱們前面打開獲取到的結(jié)果),接受切片的字節(jié),返回讀取的內(nèi)容,以及錯誤

在此函數(shù)中首先檢查是否為有效的讀取,然后在進(jìn)行f.read(b)的操作,接受其返回結(jié)果。

f.read(b)

在這里,主要檢測是否在讀取,如果是那么返回本次的讀取內(nèi)容

從以上我們不難看出,其實(shí)讀取文件是讀取文件內(nèi)部的字節(jié)

那么更具FileObj.Read(),我們可以了解它基本的使用方法,如下

func (f *File) Read(b []byte) (n int, err error)

讀取部分的示例代碼如下:

在這里我們需要考慮:是否能夠正常讀?。渴欠褡x完了?具體請看異常處理部分

// 讀取文件
    // 定義每次讀取的大小
    //var tmp = make([]byte, 128)
    var tmp  [128]byte

    // n:從開始到結(jié)尾的內(nèi)容
    n, err := fileObj.Read(tmp[:])
    // 異常處理
    if err != nil {
        fmt.Printf("Read of File Error, ErrorMessage:%#v\n", err)
        return
    }
    if err == io.EOF {
        fmt.Println("文件讀完了")
        return
    }
    fmt.Printf("讀取了%d個字節(jié)\n", n)
    fmt.Printf("讀取到的內(nèi)容:\n%s",tmp[:])

輸出結(jié)果如下:

image

以上很明顯是并沒有讀完的僅讀取了部分,原始的全部代碼如下

package main

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

func main() {
    // 打開此文件,./main.go為相對路徑。在這里是此文件
    fileObj, err := os.Open("./main.go")
    // 異常處理
    if err != nil {
        fmt.Printf("Open of File Error, ErrorMessage:%#v\n", err)
        return
    }
    // 嘗試打印(此處輸出的為地址值)
    fmt.Println(&fileObj)
    // defer 關(guān)閉文件
    defer fileObj.Close()

    // 讀取文件
    // 定義每次讀取的大小
    //var tmp = make([]byte, 128)
    var tmp  [128]byte

    // n:從開始到結(jié)尾的內(nèi)容
    n, err := fileObj.Read(tmp[:])
    // 異常處理
    if err != nil {
        fmt.Printf("Read of File Error, ErrorMessage:%#v\n", err)
        return
    }
    if err == io.EOF {
        fmt.Println("文件讀完了")
        return
    }
    fmt.Printf("讀取了%d個字節(jié)\n", n)
    fmt.Printf("讀取到的內(nèi)容:\n%s",tmp[:])
}

完整讀取

for無線循環(huán)讀取

由于以上我們并沒有讀取完整個文件,那么我需要讀取全部的該怎么辦呢?一個方法是不斷的讀取下去,然后和在一起就是完整的內(nèi)容了,示例代碼如下

package main

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

func main() {
    // 打開此文件,./main.go為相對路徑。在這里是此文件
    fileObj, err := os.Open("./main.go")
    // 異常處理
    if err != nil {
        fmt.Printf("Open of File Error, ErrorMessage:%#v\n", err)
        return
    }
    // 嘗試打印(此處輸出的為地址值)
    fmt.Println(&fileObj)
    // defer 關(guān)閉文件
    defer fileObj.Close()
    // 循環(huán)讀取文件
    var content []byte
    var tmp = make([]byte, 128)
    for {
        n, err := fileObj.Read(tmp)
        if err == io.EOF {
            fmt.Println("文件讀完了")
            break
        }
        if err != nil {
            fmt.Printf("Read of File Error, ErrorMessage:%#v\n", err)
            return
        }
        content = append(content, tmp[:n]...)
    }
    fmt.Println(string(content))
}

主要的思路為:無限循環(huán)去讀取,讀完了之后break掉。然后把讀取的內(nèi)容合并起來

這種讀取雖然可行,不過是否有點(diǎn)太麻煩了,那么有什么更簡便的方式呢?答案當(dāng)然是有的,bufio讀取

bufio讀取

bufio是在file的基礎(chǔ)上封裝了一層API,支持更多的功能。

主要的部分源碼如下所示

// bufio.NewReader
// NewReader returns a new Reader whose buffer has the default size.
func NewReader(rd io.Reader) *Reader {
    return NewReaderSize(rd, defaultBufSize)
}

// NewReaderSize
// NewReaderSize returns a new Reader whose buffer has at least the specified
// size. If the argument io.Reader is already a Reader with large enough
// size, it returns the underlying Reader.
func NewReaderSize(rd io.Reader, size int) *Reader {
    // Is it already a Reader?
    b, ok := rd.(*Reader)
    if ok && len(b.buf) >= size {
        return b
    }
    if size < minReadBufferSize {
        size = minReadBufferSize
    }
    r := new(Reader)
    r.reset(make([]byte, size), rd)
    return r
}

它簡便的原因是因?yàn)橐呀?jīng)幫我們定義了文件的指針,以及它還定義了緩沖區(qū),這樣我們使用它來讀取更加的快與便捷。

bufio.NewReader語法格式

func NewReader(rd io.Reader) *Reader 
// 其中rd為我們打開文件的對象

使用如下

package main

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

func main() {
    // 打開此文件,./main.go為相對路徑。在這里是此文件
    fileObj, err := os.Open("./main.go")
    // 異常處理
    if err != nil {
        fmt.Printf("Open of File Error, ErrorMessage:%#v\n", err)
        return
    }
    // 嘗試打印(此處輸出的為地址值)
    fmt.Println(&fileObj)
    // defer 關(guān)閉文件
    defer fileObj.Close()
    // bufio讀取
    reader := bufio.NewReader(fileObj)
    for {
        line, err := reader.ReadString('\n') //注意是字符
        if err == io.EOF {
            if len(line) != 0 {
                fmt.Println(line)
            }
            fmt.Println("文件讀完了")
            break
        }
        if err != nil {
            fmt.Println("read file failed, err:", err)
            return
        }
        fmt.Print(line)
    }
}

輸入結(jié)果如上,略。。。

搞了這么多,就沒有一鍵讀取的么?當(dāng)然也是有的,讓我們來了體驗(yàn)一下ioutil讀取整個文件的愉悅。

package main

import (
    "fmt"
    "io/ioutil"
)

// ioutil.ReadFile讀取整個文件
func main() {
    content, err := ioutil.ReadFile("./main.go")
    if err != nil {
        fmt.Println("read file failed, err:", err)
        return
    }
    fmt.Println(string(content))
}

其內(nèi)部的實(shí)現(xiàn)原理,先預(yù)測整個文件的大小。然后一次性全部讀取。當(dāng)然需要做好異常的準(zhǔn)備哦

// ReadFile reads the file named by filename and returns the contents.
// A successful call returns err == nil, not err == EOF. Because ReadFile
// reads the whole file, it does not treat an EOF from Read as an error
// to be reported.
func ReadFile(filename string) ([]byte, error) {
    f, err := os.Open(filename)
    if err != nil {
        return nil, err
    }
    defer f.Close()
    // It's a good but not certain bet that FileInfo will tell us exactly how much to
    // read, so let's try it but be prepared for the answer to be wrong.
    var n int64 = bytes.MinRead

    if fi, err := f.Stat(); err == nil {
        // As initial capacity for readAll, use Size + a little extra in case Size
        // is zero, and to avoid another allocation after Read has filled the
        // buffer. The readAll call will read into its allocated internal buffer
        // cheaply. If the size was wrong, we'll either waste some space off the end
        // or reallocate as needed, but in the overwhelmingly common case we'll get
        // it just right.
        if size := fi.Size() + bytes.MinRead; size > n {
            n = size
        }
    }
    return readAll(f, n)
}

文件寫入操作

os.OpenFile()函數(shù)能夠以指定模式打開文件,從而實(shí)現(xiàn)文件寫入相關(guān)功能。

func OpenFile(name string, flag int, perm FileMode) (*File, error) {
    ...
}

其中:

name:要打開的文件名 flag:打開文件的模式。 模式有以下幾種:

模式 含義
os.O_WRONLY 只寫
os.O_CREATE 創(chuàng)建文件
os.O_RDONLY 只讀
os.O_RDWR 讀寫
os.O_TRUNC 清空
os.O_APPEND 追加

perm:文件權(quán)限,一個八進(jìn)制數(shù)。r(讀)04,w(寫)02,x(執(zhí)行)01。

Write和WriteString

func main() {
    file, err := os.OpenFile(test.txt", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
    if err != nil {
        fmt.Println("open file failed, err:", err)
        return
    }
    defer file.Close()
    str := "hello"
    file.Write([]byte(str))       //寫入字節(jié)切片數(shù)據(jù)
    file.WriteString("hello") //直接寫入字符串?dāng)?shù)據(jù)
}

bufio.NewWriter

func main() {
    file, err := os.OpenFile("xx.txt", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
    if err != nil {
        fmt.Println("open file failed, err:", err)
        return
    }
    defer file.Close()
    writer := bufio.NewWriter(file)
    for i := 0; i < 10; i++ {
        writer.WriteString("hello") //將數(shù)據(jù)先寫入緩存
    }
    writer.Flush() //將緩存中的內(nèi)容寫入文件
}

ioutil.WriteFile

func main() {
    str := "hello"
    err := ioutil.WriteFile("./asd.txt", []byte(str), 0666)
    if err != nil {
        fmt.Println("write file failed, err:", err)
        return
    }
}

so cool~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容