Golang 文件操作的那些事兒

Os模塊的使用與源碼研究

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

文件的基本操作可簡單分為伐弹、兩類豁陆,也就是咱們所說的CURD(增刪改查),也是基于此兩類操作撞蜂◎迅可簡單理解為打開文件夾仓蛆、CURD、關(guān)閉文件夾挎春。結(jié)束~

golang對于文件基本上都是基于Golang的os模塊看疙,那讓我們一起了解一下,那么Golang是如何對文件進行操作呢直奋。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打開命名文件以供讀取能庆。如果成功,則可以使用返回文件上的方法進行讀劝锱觥相味;關(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ǔ)使用的介紹暫且為止,其實我們更應(yīng)該關(guān)心的應(yīng)該是OpenFile(name, O_RDONLY, 0)傻唾,這個函數(shù)到底干了啥投慈,我們追蹤一下這個函數(shù)(在GoLang編輯器中, mac可以直接使用command + 鼠標(biāo)左鍵直接進入冠骄,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)容還是有點分量的,有信息的伙伴锁右,可以詳細的閱讀一下全部內(nèi)容失受。暫且為止

那讓我們實踐一下,使用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)右鍵運行浦夷,這樣可能會導(dǎo)致路徑錯誤。大部分的編輯器都并不是只運行此文件!!!

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

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

讀取文件

打開文件之后,那么我們可以就可以對他們進行操作了汹来,我們在這里主要演示一下讀取文件的操作续膳。還是老樣子,先看一下主要的相關(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ù)中首先檢查是否為有效的讀取社付,然后在進行f.read(b)的操作,接受其返回結(jié)果。

f.read(b)

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

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

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

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

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

在這里我們需要考慮:是否能夠正常讀取碘饼?是否讀完了熙兔?具體請看異常處理部分

// 讀取文件
    // 定義每次讀取的大小
    //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)容合并起來

這種讀取雖然可行蛾找,不過是否有點太麻煩了娩脾,那么有什么更簡便的方式呢?答案當(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
}

它簡便的原因是因為已經(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)然也是有的,讓我們來了體驗一下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)部的實現(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ù)能夠以指定模式打開文件炼彪,從而實現(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)限辐马,一個八進制數(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)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市檩帐,隨后出現(xiàn)的幾起案子术幔,更是在濱河造成了極大的恐慌,老刑警劉巖湃密,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件诅挑,死亡現(xiàn)場離奇詭異,居然都是意外死亡泛源,警方通過查閱死者的電腦和手機拔妥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來达箍,“玉大人没龙,你說我怎么就攤上這事《忻担” “怎么了兜畸?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長碘梢。 經(jīng)常有香客問我,道長伐蒂,這世上最難降的妖魔是什么煞躬? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮逸邦,結(jié)果婚禮上恩沛,老公的妹妹穿的比我還像新娘。我一直安慰自己缕减,他們只是感情好雷客,可當(dāng)我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著桥狡,像睡著了一般搅裙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上裹芝,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天部逮,我揣著相機與錄音,去河邊找鬼嫂易。 笑死兄朋,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的怜械。 我是一名探鬼主播颅和,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼傅事,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了峡扩?” 一聲冷哼從身側(cè)響起蹭越,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎有额,沒想到半個月后般又,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡巍佑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年茴迁,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片萤衰。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡堕义,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出脆栋,到底是詐尸還是另有隱情倦卖,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布椿争,位于F島的核電站怕膛,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏秦踪。R本人自食惡果不足惜褐捻,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望椅邓。 院中可真熱鬧柠逞,春花似錦、人聲如沸景馁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽合住。三九已至绰精,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間透葛,已是汗流浹背茬底。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留获洲,地道東北人阱表。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親最爬。 傳聞我的和親對象是個殘疾皇子涉馁,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內(nèi)容