golang buf包就這么用

bufio

它的作用用一句話表述就是:

利用緩沖區(qū)減少io操作次數(shù),提升讀寫性能。

1. 為什么要用bufio?

開(kāi)始之前我們先來(lái)看一段代碼:

package main

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

func main() {
    // 讀取當(dāng)前目錄 data.txt文件內(nèi)容
    file, err := os.Open("./data.txt")
    if err != nil {
        fmt.Println("打開(kāi)文件錯(cuò)誤:", err)
        return
    }
    defer file.Close()

    data := make([]byte, 3)
    // 讀取10次 每次讀取3個(gè)字節(jié)
    for i := 0; i < 10; i++ {
        _, err := file.Read(data)

        // 遇到文件結(jié)束
        if err == io.EOF {
            fmt.Println(err)
            break
        }
        fmt.Println(string(data))
    }
}

上面實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的文件讀取功能,能正常工作,但是有一個(gè)有一個(gè)問(wèn)題,每次從文件讀取3個(gè)字節(jié),而且讀取了10次,也就是讀取了3 * 10 = 30個(gè)字節(jié)的數(shù)據(jù),卻做了10次io操作,性能可想而知。

那么我們?nèi)绾蝺?yōu)化呢?
請(qǐng)出我們的主角bufio,它的主要作用是:減少io操作次數(shù),提供讀寫性能。

我們用bufio優(yōu)化下

package main

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

func main() {
    // 讀取當(dāng)前目錄 data.txt文件內(nèi)容
    file, err := os.Open("./data.txt")
    if err != nil {
        fmt.Println("打開(kāi)文件錯(cuò)誤:", err)
        return
    }
    defer file.Close()

    // 用bufio封裝一層 返回一個(gè)reader
    reader := bufio.NewReader(file)

    data := make([]byte, 3)
    // 讀取10次 每次讀取3個(gè)字節(jié)
    for i := 0; i < 10; i++ {
        _, err := reader.Read(data) // 這里改成從reader中讀

        // 遇到文件結(jié)束
        if err == io.EOF {
            fmt.Println(err)
            break
        }
        fmt.Println(string(data))
    }
}

優(yōu)化很簡(jiǎn)單總共兩步:

  1. bufio封裝一層返回一個(gè)reader
  2. bufio.Reader去替換原來(lái)的直接文件(io.Reader)讀

2. bufio緩沖區(qū)讀寫原理

首先bufio的主要對(duì)象是緩沖區(qū),操作主要有兩個(gè):

記住,它底層的所有東西都圍繞讀、寫展開(kāi)。

原理上,我們也按照讀、寫來(lái)分別說(shuō)明:

PS: 下面流程只是一個(gè)大概參考,不代表全部邏輯

 
 讀取長(zhǎng)度小于緩沖區(qū)大小,從緩沖區(qū)讀取
 1.----------------->
                    當(dāng)緩沖區(qū)為空,直接從文件讀取,填滿緩沖區(qū)
                    2. -------------->
 【程序】           【緩沖區(qū)】           【文件(io.Reader)】
  
  3. 讀取長(zhǎng)度超過(guò)緩沖區(qū)大小,直接從文件讀取
  ----------------------------------> 
 寫長(zhǎng)度小于緩沖大小,先寫入緩沖區(qū)
 1.----------------->
                    當(dāng)緩沖區(qū)滿,觸發(fā)寫入到文件
                    2. -------------->
 【程序】           【緩沖區(qū)】           【文件(io.Reader)】
  
  3. 寫長(zhǎng)度超過(guò)緩沖區(qū)大小,直接寫入文件
  -----------------------------------> 

在bufio內(nèi)部實(shí)現(xiàn)的reader和writer,大致是按照上述邏輯處理的,還有些細(xì)節(jié)的東西,沒(méi)有在上面畫出,但是做為初學(xué)者,了解下就行。

3. bufio讀

在介紹之前,先說(shuō)明一點(diǎn),無(wú)論是讀還是寫,其構(gòu)造過(guò)程都是差不多的:

  1. NewReader/NewWriter構(gòu)造一個(gè)讀/寫對(duì)象
  2. 傳入一個(gè)實(shí)現(xiàn)了io.Reader/io.Writer的對(duì)象

1. 構(gòu)造bufio讀對(duì)象

只要是實(shí)現(xiàn)了io.Reader對(duì)象都可以,比如:

// =================1.從文件==============
file, err := os.Open("./data.txt")
if err != nil {
fmt.Println("打開(kāi)文件錯(cuò)誤:", err)
return
}
defer file.Close()

reader := bufio.NewReader(file)

// =================2. 從字符串=========
strReader := strings.NewReader("hello world")
bufio.NewReader(strReader)

// =================3. 從網(wǎng)絡(luò)鏈接=======
bufio.NewReader(conn)

這里就不一一列舉了。

2. Read讀

和直接從原始對(duì)象讀一樣

package main

import (
    "bufio"
    "fmt"
    "strings"
)

func main() {
    strReader := strings.NewReader("hello world")
    buf := bufio.NewReader(strReader)

    // 讀前要構(gòu)造一個(gè)切片 用于存放讀取的內(nèi)容
    data := make([]byte, 5)
    // 讀取數(shù)據(jù)到data
    _, err := buf.Read(data)

    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(string(data)) // 轉(zhuǎn)字符串打印
}

// hello

3. ReadLine 按照行讀取

有兩點(diǎn)需要注意:

  1. 它返回三個(gè)參數(shù) line、isPrefix、err
  2. 如果一行太長(zhǎng)本次沒(méi)讀取完,則isPrefix會(huì)是true
  3. 返回的文本不包括行尾("\r\n"或"\n")

ps: 官方更推薦使用ReadString/ReadBytes/Scaner

package main

import (
    "bufio"
    "fmt"
    "io"
    "strings"
)

func main() {
    str := `
     大家好
     非常好
     非常非常好
    `
    strReader := strings.NewReader(str)
    buf := bufio.NewReader(strReader)

    for {
        // 返回三個(gè)參數(shù) line、是否前綴、錯(cuò)誤
        line, _, err := buf.ReadLine()
        // 結(jié)束直接返回
        if err == io.EOF {
            fmt.Println("結(jié)束啦")
            break
        }

        // 字符串直接打印
        fmt.Println(string(line))
    }
}

// 大家好
// 非常好
// 非常非常好
// 結(jié)束啦

4. ReadString 直接讀出字符串

它有兩個(gè)好處:

  1. 直接返回字符串,省得轉(zhuǎn)換
  2. 不用事先構(gòu)造一個(gè)切片來(lái)裝讀取到的數(shù)據(jù)

注意它讀取后的內(nèi)容里是包含分割符號(hào)的

package main

import (
    "bufio"
    "fmt"
    "io"
    "strings"
)

func main() {
    str := `
     大家好
     非常好
     非常非常好
    `
    strReader := strings.NewReader(str)
    buf := bufio.NewReader(strReader)

    for {
        // 這里是一個(gè)分割符
        s, err := buf.ReadString('\n')
        // 結(jié)束直接返回
        if err == io.EOF {
            fmt.Println("結(jié)束啦")
            break
        }

        // 字符串直接打印
        fmt.Printf(s)
    }
}

// 大家好
// 非常好
// 非常非常好
// 結(jié)束啦

這里還有幾個(gè)類似的方法,非常接近,就不單獨(dú)演示了
區(qū)別在于,ReadBytes 它返回一個(gè)字節(jié)切片([]byte)

5. Scanner 掃描

特點(diǎn):

  1. 自己定義一個(gè)掃描函數(shù),然后按照規(guī)則掃描;如果不指定掃描器,它和單獨(dú)按照行讀取類型;
  2. 返回內(nèi)容不包含換行符
package main

import (
    "bufio"
    "fmt"
    "strings"
)

func main() {
    str := `
     大家好
     非常好
     非常非常好
    `
    strReader := strings.NewReader(str)
    // 先生成一個(gè)Scanner
    scanner := bufio.NewScanner(strReader)

    // 掃描每行
    for scanner.Scan() {
        // 返回的是一個(gè)字符串
        content := scanner.Text()
        fmt.Println(content)
    }

    // 檢查掃描過(guò)程是否報(bào)錯(cuò)
    if err := scanner.Err(); err != nil {
        fmt.Println("掃描過(guò)程發(fā)生了錯(cuò)誤:", err.Error())
    }
}

4. bufio 寫

緩沖區(qū)默認(rèn)大小為4K(4096字節(jié))
這里需要注意的是,如果緩沖區(qū)沒(méi)有滿,不會(huì)自動(dòng)寫入io;
我們可以手動(dòng)Flush 完成寫入

先看下代碼:

package main

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

func main() {

    // os.O_RDWR|os.O_CREATE 讀寫 如果不存在則創(chuàng)建
    file, err := os.OpenFile("data.txt", os.O_RDWR|os.O_CREATE, 0666)
    if err != nil {
        fmt.Println(err)
        return
    }

    defer file.Close()
    // 構(gòu)造緩沖寫
    buf := bufio.NewWriter(file)

    // 三次write寫入緩沖
    buf.Write([]byte("hello world\n"))
    buf.Write([]byte("非常美麗\n"))
    buf.Write([]byte("不錯(cuò)吧\n"))

    // 直接寫入文件
    buf.Flush()
}

1. 構(gòu)造writer

//直接用io.Writer構(gòu)造
buf := bufio.NewWriter(file)

// 指定緩沖大小 (最小是16字節(jié))
buf := bufio.NewWriterSize(file, 30)

2. 各種wirter方式

主要有以下幾種方式:

// 以字符串方式寫入
buf.WriteString("來(lái)吧來(lái)吧來(lái)\n")
    
// 一次寫一個(gè)rune字符 返回實(shí)際占用的字節(jié)數(shù)
n, _ := buf.WriteRune('中')
c, _ := buf.WriteRune('\n')

// 一次寫入一個(gè)byte
buf.WriteByte('a')
buf.WriteByte('A')

3. Flush寫入io

// 直接寫入io
buf.Flush()

4. 其它

// 重置buf 此前緩沖中的數(shù)據(jù)都被清理掉 
buf.Reset(os.Stdout)

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

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

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