溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

Go36-42,43-bufio包

發(fā)布時(shí)間:2020-06-16 15:29:31 來(lái)源:網(wǎng)絡(luò) 閱讀:343 作者:騎士救兵 欄目:編程語(yǔ)言

bufio包

這是另一個(gè)與I/O操作強(qiáng)相關(guān)的代碼包。bufio是“buffered I/O”的縮寫(xiě),這個(gè)代碼包中的程序?qū)嶓w實(shí)現(xiàn)的I/O操作都內(nèi)置了緩沖區(qū)。

主要數(shù)據(jù)類(lèi)型

bufio包中的數(shù)據(jù)類(lèi)型主要有:

  • Reader
  • Scanner
  • Writer和ReadWriter

與io包中的數(shù)據(jù)類(lèi)型類(lèi)似,這些類(lèi)型的值也都需要在初始化的時(shí)候,包裝一個(gè)或多個(gè)簡(jiǎn)單I/O接口類(lèi)型的值。這里的簡(jiǎn)單I/O接口類(lèi)型指的就是io包中的那些簡(jiǎn)單接口。

緩沖區(qū)的作用(bufio.Reader)

bufio.Reader類(lèi)型的值內(nèi)的緩沖區(qū)其實(shí)就是一個(gè)數(shù)據(jù)存儲(chǔ)中介,它介于底層讀取器與讀取方法及其調(diào)用方之間。所謂的底層讀取器,就是在初始化此類(lèi)值的時(shí)候傳入的io.Reader類(lèi)型的參數(shù)值。
Reader值的讀取方法一般會(huì)先從其所屬值的緩沖區(qū)中讀取數(shù)據(jù)。同時(shí),在必要的時(shí)候,還會(huì)預(yù)先從底層讀取器那里讀出一部分?jǐn)?shù)據(jù),并暫存于緩沖區(qū)之中以備后用。有這樣一個(gè)緩沖區(qū)的好處是,可以在大多數(shù)的時(shí)候降低讀取方法的執(zhí)行時(shí)間。雖然讀取方法有時(shí)還要負(fù)責(zé)填充緩沖區(qū),但從總體來(lái)看,讀取方法的平均執(zhí)行時(shí)間一般都會(huì)因此有大幅度的縮短。

bufio.Reader結(jié)構(gòu)體中的字段

bufio.Reader類(lèi)型并不是開(kāi)箱即用的,因?yàn)樗艘恍┰S可顯示初始化的字段。結(jié)構(gòu)體的定義如下:

type Reader struct {
    buf          []byte
    rd           io.Reader // reader provided by the client
    r, w         int       // buf read and write positions
    err          error
    lastByte     int
    lastRuneSize int
}

簡(jiǎn)要的解釋一下結(jié)構(gòu)體中的字段:

  1. buf,字節(jié)切片,代表緩沖區(qū)。雖然它是切片類(lèi)型,但是其長(zhǎng)度會(huì)在初始化的時(shí)候指定,并且之后保持不變。
  2. rd,代表底層讀取器。緩沖區(qū)中的數(shù)據(jù)就是從這里拷貝出來(lái)的。
  3. r,代表對(duì)緩沖區(qū)進(jìn)行下一次讀取時(shí)的開(kāi)始索引??梢苑Q(chēng)它為已讀計(jì)數(shù)。
  4. w,代表對(duì)緩沖區(qū)進(jìn)行下一次寫(xiě)入是的開(kāi)始縮寫(xiě)??梢苑Q(chēng)它為已寫(xiě)計(jì)數(shù)。
  5. err,它的值表示在從底層讀取器獲得數(shù)據(jù)時(shí)發(fā)生的錯(cuò)誤。這里的值在被讀取或忽略之后,該字段會(huì)被置為nil。
  6. lastByte,記錄緩沖區(qū)最后一個(gè)被讀取的字節(jié)。讀回退時(shí)會(huì)用到它的值。
  7. lastRuneSize,記錄緩沖區(qū)最后一個(gè)被讀取的Unicode字符所占用的字節(jié)數(shù)。讀回退的時(shí)候會(huì)用到它的值。這個(gè)字段只會(huì)在其所屬值的ReadRune方法中才會(huì)被賦予有意義的值。其他它情況都會(huì)被置為-1。

初始化函數(shù)

bufio包提供了兩個(gè)用于用于初始化Reader值的函數(shù),都會(huì)返回一個(gè)*bufio.Reader類(lèi)型的值:

  • NewReader
  • NewReaderSze

NewReader函數(shù)初始化的Reader值會(huì)擁有一個(gè)默認(rèn)尺寸的緩沖區(qū)。這個(gè)默認(rèn)尺寸是4096個(gè)字節(jié),即:4KB:

const (
    defaultBufSize = 4096
)

func NewReader(rd io.Reader) *Reader {
    return NewReaderSize(rd, defaultBufSize)
}

func NewReaderSize(rd io.Reader, size int) *Reader {
    // 內(nèi)部代碼省略
}

NewReaderSize函數(shù)則將緩沖區(qū)尺寸的決定權(quán)拋給了使用方。從上面的源碼看,NewReader函數(shù)就是調(diào)用NewReaderSize的時(shí)候,指定了第二個(gè)用于決定緩沖區(qū)尺寸的參數(shù)。初始化函數(shù)的示例:

func main() {
    comment := "TEST"
    basicReader := strings.NewReader(comment)
    fmt.Println(basicReader.Size())
    reader1 := bufio.NewReader(basicReader)
    fmt.Println(reader1.Size())
    reader2 := bufio.NewReaderSize(basicReader, 128)
    fmt.Println(reader2.Size())
}

由于這里的緩沖區(qū)在一個(gè)Reader值的聲明周期內(nèi)其尺寸不可變,所以在有些時(shí)候是需要做一些權(quán)衡的。NewReaderSize函數(shù)就提供了這樣一個(gè)途徑。

填充緩沖區(qū)(fill方法)

在bufio.Reader類(lèi)型擁有的讀取方法中,Peek方法和ReadSlice方法都會(huì)調(diào)用該類(lèi)型的一個(gè)名為fill的包級(jí)私有方法。fill方法的作用是填充內(nèi)部緩沖區(qū)。
fill方法會(huì)先檢查其所屬值的已讀計(jì)數(shù)。如果這個(gè)計(jì)數(shù)不大于0,那么有兩種可能:

  • 緩沖區(qū)中的字節(jié)都是全新的,就是它們都沒(méi)有被讀取過(guò)
  • 緩沖區(qū)剛被壓縮過(guò)

壓縮
緩沖區(qū)的壓縮包括兩個(gè)步驟:

  1. 把緩沖區(qū)中在[已讀計(jì)數(shù), 已寫(xiě)計(jì)數(shù))范圍之內(nèi)的所有元素值(或者說(shuō)字節(jié))都依次拷貝到緩沖區(qū)的頭部
  2. 把已寫(xiě)計(jì)數(shù)的新值設(shè)定為原已寫(xiě)計(jì)數(shù)與原已讀計(jì)數(shù)的差。這個(gè)差代表的索引,就是壓縮后第一次寫(xiě)入字節(jié)時(shí)的開(kāi)始索引

另外,fill方法還會(huì)把已讀計(jì)數(shù)的值置為0,顯然,在壓縮之后,再讀取字節(jié)就是從緩沖區(qū)的頭部開(kāi)始讀了。
實(shí)際上,fill方法只要在開(kāi)始時(shí)發(fā)現(xiàn)其所屬值的已讀計(jì)數(shù)大于0,就會(huì)對(duì)緩沖區(qū)進(jìn)行一次壓縮。之后,如果緩沖區(qū)還有可寫(xiě)的位置,那么該方法就會(huì)對(duì)其進(jìn)行填充。

填充
在填充緩沖區(qū)的時(shí)候,fill方法會(huì)試圖從底層讀取器那里,讀取足夠多的字節(jié),并盡量把從已寫(xiě)計(jì)數(shù)代表的索引位置到緩沖區(qū)末尾之間的空間都填滿。在這個(gè)過(guò)程中,fill方法會(huì)及時(shí)的更新已寫(xiě)計(jì)數(shù),以保證填充的正確性和順序性。另外,它還會(huì)判斷從底層讀取器讀取數(shù)據(jù)的時(shí)候,是否有錯(cuò)誤發(fā)生。如果有,那么它就會(huì)把錯(cuò)誤值賦給其所屬值的err字段,并終止填充流程。

示例代碼
下面是一個(gè)Peek方法使用的示例:

package main

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

func main() {
    comment := "Hello, World!"
    basicReader := strings.NewReader(comment)
    fmt.Printf("字符串長(zhǎng)度: %d\n", basicReader.Size())
    reader := bufio.NewReader(basicReader)
    fmt.Println("緩沖區(qū)長(zhǎng)度:", reader.Size())
    // 此時(shí)緩沖區(qū)還沒(méi)有被填充
    fmt.Println("緩沖區(qū)里的未讀字節(jié)數(shù):", reader.Buffered())

    bytes, err := reader.Peek(5)
    if err != nil {
        fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
    }
    fmt.Printf("Peek讀取(%d): %q\n", len(bytes), bytes)
    fmt.Println("緩沖區(qū)里的未讀字節(jié)數(shù):", reader.Buffered())
}

bufio.Writer

bufio.Writer類(lèi)型有一個(gè)Flush方法,它的主要功能是把相應(yīng)緩沖區(qū)中暫存的所有數(shù)據(jù),都寫(xiě)到底層寫(xiě)入器中。數(shù)據(jù)一旦被寫(xiě)進(jìn)底層寫(xiě)入器,該方法就會(huì)把這些數(shù)據(jù)從緩沖區(qū)中刪除掉。這里的刪除有時(shí)候只是邏輯上的刪除而已。不論是否成功的寫(xiě)入了所有的暫存數(shù)據(jù),F(xiàn)lush方法都會(huì)妥當(dāng)處置,并保證不會(huì)出現(xiàn)重寫(xiě)和漏寫(xiě)的情況。該類(lèi)型的字段n在此會(huì)起到很重要的作用。

bufio.Writer結(jié)構(gòu)體中的字段

bufio.Writer結(jié)構(gòu)體的定義如下:

type Writer struct {
    err error
    buf []byte
    n   int
    wr  io.Writer
}

字段說(shuō)明:

  1. err,用于表示在向底層寫(xiě)上器寫(xiě)數(shù)據(jù)時(shí)發(fā)生的錯(cuò)誤。
  2. buf,代表緩沖區(qū)。在初始化之后,它的長(zhǎng)度會(huì)保持不變。
  3. n,代表對(duì)緩沖區(qū)進(jìn)行下一次寫(xiě)入時(shí)的開(kāi)始索引。可以稱(chēng)之為已寫(xiě)計(jì)數(shù)。
  4. wr,代表底層寫(xiě)入器。

Flush方法

bufio.Writer類(lèi)型的值擁有的所有數(shù)據(jù)寫(xiě)入方法都會(huì)在必要的時(shí)候調(diào)用Flush方法。
比如,Write方法有時(shí)候會(huì)在把數(shù)據(jù)寫(xiě)進(jìn)緩沖區(qū)之后,調(diào)用Flush方法,以便為后續(xù)的新數(shù)據(jù)騰出空間。WriteString方法的行為與之類(lèi)似。
又比如,WriteByte方法和WriteRune方法,都會(huì)在發(fā)現(xiàn)緩沖區(qū)的可寫(xiě)空間不足以容納新的字節(jié)或Unicode字符的時(shí)候,調(diào)用Flush方法。
此外,如果Write方法發(fā)現(xiàn)需要寫(xiě)入的字節(jié)太多,同時(shí)緩沖區(qū)已空,那么它就會(huì)跨過(guò)緩沖區(qū),并直接把這些數(shù)據(jù)寫(xiě)到底層寫(xiě)入器中。
而ReadFrom,則會(huì)在發(fā)現(xiàn)底層寫(xiě)入器的類(lèi)型是io.ReaderFrom接口的實(shí)現(xiàn)之后,直接調(diào)用其ReadFrom方法把參數(shù)值持有的數(shù)據(jù)寫(xiě)進(jìn)去。
下面是一些示例代碼:

package main

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

func main() {
    comment := "Go is an open source programming language that makes it easy to build simple, " +
        "reliable,  " +
        "and efficient software."
    fmt.Println("全部的字節(jié)數(shù):", len(comment))  // 112
    basicWriter1 := &strings.Builder{}
    size := 64
    writer1 := bufio.NewWriterSize(basicWriter1, size)
    fmt.Println("緩沖區(qū)大小:", size)
    fmt.Println()

    // WriteString方法調(diào)用Flush后,騰出空間
    start, end := 0, 41
    fmt.Println("寫(xiě)入字節(jié)數(shù):", end-start)
    writer1.WriteString(comment[start:end])
    fmt.Println("緩沖區(qū)使用字節(jié)數(shù):", writer1.Buffered())
    fmt.Println("緩沖區(qū)可用字節(jié)數(shù):", writer1.Available())
    fmt.Println("Flush方法刷新緩沖區(qū)...")
    writer1.Flush()
    fmt.Println("緩沖區(qū)使用字節(jié)數(shù):", writer1.Buffered())
    fmt.Println("緩沖區(qū)可用字節(jié)數(shù):", writer1.Available())
    fmt.Println()

    // 寫(xiě)入的字節(jié)太多,
    start, end = 0, len(comment)  // 全部讀完,所有的字節(jié)數(shù)大于緩沖區(qū)的大小
    fmt.Println("寫(xiě)入字節(jié)數(shù):", end-start)
    writer1.WriteString(comment[start:end])
    fmt.Println("緩沖區(qū)使用字節(jié)數(shù):", writer1.Buffered())
    fmt.Println("緩沖區(qū)可用字節(jié)數(shù):", writer1.Available())
    fmt.Println("Flush方法刷新緩沖區(qū)...")
    writer1.Flush()
    fmt.Println()

    // ReadFrom會(huì)走捷徑,不使用緩沖區(qū)
    basicWriter2 := &bytes.Buffer{}
    writer1.Reset(basicWriter2)
    reader := strings.NewReader(comment)
    writer1.ReadFrom(reader)
    fmt.Println("緩沖區(qū)使用字節(jié)數(shù):", writer1.Buffered())
    fmt.Println("緩沖區(qū)可用字節(jié)數(shù):", writer1.Available())
}

總之,在通常情況下,只要緩沖區(qū)中的可寫(xiě)空間無(wú)法容納需要寫(xiě)入的新數(shù)據(jù),F(xiàn)lush方法就一定會(huì)被調(diào)用。并且,bufio.Writer類(lèi)型的一些方法有時(shí)候還會(huì)試圖走捷徑,跨過(guò)緩沖區(qū)而直接對(duì)接數(shù)據(jù)供需的雙方??梢栽诶斫饬诉@些內(nèi)部機(jī)制之后,明確的在代碼里使用Flush方法。不過(guò),也可以在把所有的數(shù)據(jù)都寫(xiě)入Writer值之后,再調(diào)用一下它的Flush方法,這是最穩(wěn)妥的做法。

讀取方法

bufio.Reader類(lèi)型擁有很多用于讀取數(shù)據(jù)的指針?lè)椒?,其中?個(gè)方法可以作為不同讀取流程的代表:

  • Peek
  • Read
  • ReadSlice
  • ReadBytes

Peek方法

Peek方法的功能是:讀取并返回其緩沖區(qū)中的n個(gè)未讀字節(jié),并且它會(huì)從已讀計(jì)數(shù)代表的索引位置開(kāi)始讀。Peek方法還有一個(gè)特點(diǎn)。就是即使它讀取了緩沖區(qū)中的數(shù)據(jù),也不會(huì)更改已讀計(jì)數(shù)。
在緩沖區(qū)未被填滿,并且其中的未讀字節(jié)的數(shù)量小于n的時(shí)候,該方法就會(huì)調(diào)用fill方法,以啟動(dòng)緩沖區(qū)填充流程。但是,如果發(fā)現(xiàn)上次填充緩沖區(qū)的時(shí)候有錯(cuò)誤,就不會(huì)再次填充了。
Peek方法的使用示例:

package main

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

func main() {
    comment := "Go is an open source programming language that makes it easy to build simple, " +
        "reliable,  " +
        "and efficient software."
    basicReader := strings.NewReader(comment)
    fmt.Println("字符串長(zhǎng)度:", basicReader.Size())
    size := 64
    reader := bufio.NewReaderSize(basicReader, size)
    fmt.Println("緩沖區(qū)長(zhǎng)度:", reader.Size())
    // 此時(shí)緩沖區(qū)還沒(méi)有被填充
    fmt.Println("緩沖區(qū)里的未讀字節(jié)數(shù):", reader.Buffered())
    fmt.Println()

    peekNum := 41
    bytes, err := reader.Peek(peekNum)
    if err != nil {
        fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
    }
    fmt.Printf("Peek讀取(%d): %q\n", len(bytes), bytes)
    fmt.Println("緩沖區(qū)里的未讀字節(jié)數(shù):", reader.Buffered())
    fmt.Println()

    // Peek方法不改變已讀計(jì)數(shù)
    // 把上面用Peek方法讀取的過(guò)程封裝一下,反復(fù)調(diào)用
    peek(reader, 2)
    peek(reader, 5)
    peek(reader, 8)
}

func peek(reader *bufio.Reader, n int) {
    bytes, err := reader.Peek(n)
    if err != nil {
        fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
    }
    fmt.Printf("Peek讀取(%d): %q\n", len(bytes), bytes)
}

最開(kāi)始,緩沖區(qū)為空,未讀字節(jié)數(shù)量為0。調(diào)用Peek方法要讀取41個(gè)字節(jié)。此時(shí)就會(huì)啟動(dòng)緩沖區(qū)填充流程。緩沖區(qū)會(huì)被填滿,這里緩沖區(qū)的大小設(shè)定為64,也就是填滿了64個(gè)字節(jié)。然后讀取了41個(gè)字節(jié)。由于Peek方法不會(huì)改變已讀計(jì)數(shù),所以緩沖區(qū)里的所有內(nèi)容都是未讀的。所以,就算反復(fù)調(diào)用Peek方法,讀到的內(nèi)容也都是一樣的。
如果調(diào)用方法給定的n比緩沖區(qū)的長(zhǎng)度還要大,或者緩沖區(qū)中未讀字節(jié)的數(shù)量小于n,那么Peek方法就會(huì)把所有未讀字節(jié)返回,并且還會(huì)返回一個(gè)錯(cuò)誤:

package main

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

func main() {
    comment := "Hello, World!"
    basicReader := strings.NewReader(comment)

    // 緩沖區(qū)中未讀字節(jié)數(shù)小于Peek方法指定的n
    reader1 := bufio.NewReader(basicReader)
    peekNum := len(comment) + 1
    bytes, err := reader1.Peek(peekNum)
    if err != nil {
        fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
    }
    fmt.Printf("緩沖區(qū)中未讀字節(jié)數(shù): %d, Peek讀取: %d\n", reader1.Buffered(), peekNum)
    fmt.Printf("Peek讀取(%d): %q\n", len(bytes), bytes)
    fmt.Println()

    // Peek方法指定的n比緩沖區(qū)長(zhǎng)度還要大
    basicReader.Reset(comment)
    size := 300
    reader2 := bufio.NewReaderSize(basicReader, size)
    peekNum = size + 1
    bytes, err = reader2.Peek(peekNum)
    if err != nil {
        fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
    }
    fmt.Printf("緩沖區(qū)長(zhǎng)度: %d, Peek讀取: %d\n", size, peekNum)
    fmt.Printf("Peek讀取(%d): %q\n", len(bytes), bytes)
}

這里兩種讀取錯(cuò)誤的情況,都能正常返回讀取的內(nèi)容。不過(guò)同時(shí),還會(huì)返回一非nil的錯(cuò)誤值。

Read方法

Read方法,在緩沖區(qū)中還有未讀字節(jié)的情況下,它會(huì)把緩沖區(qū)中的未讀字節(jié),依次拷貝到其參數(shù)p代表的字節(jié)切片中,并立即根據(jù)實(shí)際拷貝的字節(jié)數(shù)增加已讀計(jì)數(shù)的值。
不過(guò)在另外一種情況下,其所屬值的已讀計(jì)數(shù)會(huì)等于已寫(xiě)計(jì)數(shù),這說(shuō)明緩沖區(qū)中已經(jīng)沒(méi)有任何未讀的字節(jié)了。此時(shí)Read方法會(huì)先檢查參數(shù)p的長(zhǎng)度是否大于或等于緩沖區(qū)的長(zhǎng)度。
如果緩沖區(qū)中已無(wú)未讀字節(jié),參數(shù)p的長(zhǎng)度大于或等于緩沖區(qū)的長(zhǎng)度。那么會(huì)放棄向緩沖區(qū)中填充數(shù)據(jù),轉(zhuǎn)而直接從起底層讀取器讀出數(shù)據(jù)并拷貝到p中。這意味著它完全跨如果緩沖區(qū),并直連了數(shù)據(jù)供需的雙方。
如果緩沖區(qū)中已無(wú)未讀字節(jié),緩沖區(qū)長(zhǎng)度比參數(shù)p的長(zhǎng)度更大。那么會(huì)先把已讀計(jì)數(shù)和已寫(xiě)計(jì)數(shù)的值都重置為0,然后再?lài)L試使用從底層讀取器里獲取的數(shù)據(jù),對(duì)緩沖區(qū)進(jìn)行一次從頭至尾的填充。不過(guò)要注意,這里的嘗試只會(huì)進(jìn)行一次。無(wú)論在這一時(shí)刻是否能夠獲取到數(shù)據(jù),也無(wú)論獲取是是否有錯(cuò)誤發(fā)生。而這與fill方法的做法不同,只要沒(méi)有發(fā)生錯(cuò)誤,fill方法就會(huì)進(jìn)行多次嘗試,因此fill方法真正獲取到一些數(shù)據(jù)的可能性更大。所以Read方法中沒(méi)有調(diào)用fill方法,而是有一段自己的代碼實(shí)現(xiàn)緩沖區(qū)的填充。而這兩個(gè)方法進(jìn)行填充時(shí)的共同點(diǎn)是,只要把獲取到的數(shù)據(jù)寫(xiě)入緩沖區(qū),就會(huì)及時(shí)的更新已寫(xiě)計(jì)數(shù)的值。
Read方法的使用示例:

package main

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

func main() {
    comment := "Hello, World!"
    basicReader := strings.NewReader(comment)
    fmt.Println("字符串長(zhǎng)度:", basicReader.Size())
    reader := bufio.NewReader(basicReader)
    buf := make([]byte, 5)
    n, err := reader.Read(buf)
    if err != nil {
        fmt.Fprintf(os.Stderr, "ERROE: %v\n", err)
    }
    fmt.Printf("Read讀取(%d): %q\n", n, buf)
    fmt.Println("緩沖區(qū)里的未讀字節(jié)數(shù):", reader.Buffered())
}

ReadSlice方法

ReadSlice方法的功能是:持續(xù)的讀取數(shù)據(jù),直至遇到調(diào)用方給定的分隔符為止。
ReadSlice方法,會(huì)先在緩沖區(qū)的未讀部分中尋找分隔符。如果未能找到,并且緩沖區(qū)未滿,那么該方法會(huì)先通過(guò)調(diào)用fill方法對(duì)緩沖區(qū)進(jìn)行填充,然后再次尋找。如果在填充過(guò)程中發(fā)生了錯(cuò)誤(應(yīng)該包括讀到結(jié)尾了返回EOF錯(cuò)誤),那么會(huì)把緩沖區(qū)中的未讀部分作為結(jié)果返回,同時(shí)返回相應(yīng)的錯(cuò)誤值。
在上面的過(guò)程中,可能會(huì)出現(xiàn)雖然緩沖區(qū)已填滿,但是仍然沒(méi)能找到分隔符的情況。ReadSlice方法會(huì)把緩沖區(qū)里全部的內(nèi)容返回,并返回緩沖區(qū)已滿的錯(cuò)誤。此時(shí)的緩沖區(qū)是經(jīng)過(guò)fill方法填充的,肯定從頭至尾都只包含未讀的字節(jié),所以這樣做是合理的。
如果ReadSlice方法找到了分隔符,就會(huì)在緩沖區(qū)上切除相應(yīng)的、包含分隔符的字節(jié)切片,并把該切片作為結(jié)果值返回。無(wú)論分隔符是否找到,該方法都會(huì)正確的設(shè)置已讀計(jì)數(shù)的值。
ReadSlice方法的使用示例:

package main

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

func main() {
    comment := "Go is an open source programming language that makes it easy to build simple, " +
        "reliable,  " +
        "and efficient software."
    basicReader := strings.NewReader(comment)
    reader := bufio.NewReader(basicReader)
    delimiter := byte(',')
    line, err := reader.ReadSlice(delimiter)
    if err != nil {
        fmt.Fprintf(os.Stderr, "EEEOR: %v\n", err)
    }
    fmt.Printf("ReadSlice讀取(%d): %q\n", len(line), line)
    fmt.Println("緩沖區(qū)里的未讀字節(jié)數(shù):", reader.Buffered())
    fmt.Println()

    delimiter = byte('!')  // 讀不到這個(gè)分隔符
    line, err = reader.ReadSlice(delimiter)
    if err != nil {
        fmt.Fprintf(os.Stderr, "EEEOR: %v\n", err)
    }
    fmt.Printf("ReadSlice讀取(%d): %q\n", len(line), line)
    fmt.Println("緩沖區(qū)里的未讀字節(jié)數(shù):", reader.Buffered())
    fmt.Println()

    basicReader.Reset(comment)
    reader2 := bufio.NewReaderSize(basicReader, 80)
    delimiter = byte('!')  // 讀不到這個(gè)分隔符
    line, err = reader2.ReadSlice(delimiter)
    if err != nil {
        fmt.Fprintf(os.Stderr, "EEEOR: %v\n", err)
    }
    fmt.Printf("ReadSlice讀取(%d): %q\n", len(line), line)
    fmt.Println("緩沖區(qū)里的未讀字節(jié)數(shù):", reader2.Buffered())
}

這個(gè)示例里也演示了,讀完全部?jī)?nèi)容都沒(méi)有找到分隔符,以及緩沖區(qū)已滿并且其中沒(méi)有包含分隔符這兩種錯(cuò)誤的情況。

ReadBytes方法

ReadBytes方法是基于ReadSlice方法實(shí)現(xiàn)的,它的內(nèi)部會(huì)調(diào)用ReadSlice方法。
ReadSlice方法有一個(gè)問(wèn)題,它是一個(gè)容易半途而廢的方法。它可能會(huì)因?yàn)榫彌_區(qū)已滿而返回所有已讀到的字節(jié)和相應(yīng)的錯(cuò)誤值,之后不會(huì)繼續(xù)尋找。而ReadBytes方法就相當(dāng)執(zhí)著,它會(huì)通過(guò)調(diào)用ReadSlice方法一次又一次的從緩沖區(qū)中讀取數(shù)據(jù)(源碼里是一個(gè)無(wú)限for循環(huán)調(diào)用ReadSlice方法),直至找到分隔符為止。在這個(gè)過(guò)程中,ReadSlice方法可能會(huì)因?yàn)榫彌_區(qū)已滿而返回所有已讀到的字節(jié)和響應(yīng)的錯(cuò)誤值,但ReadBytes方法會(huì)忽略掉這樣的錯(cuò)誤,并再次調(diào)用ReadSlice方法,這樣就會(huì)繼續(xù)填充緩沖區(qū)并尋找分隔符。除非ReadSlice方法返回的錯(cuò)誤值不是緩沖區(qū)已滿(errors.New("bufio: buffer full")),或者它找到了分隔符(返回錯(cuò)誤值nil),否則這個(gè)過(guò)程就不會(huì)結(jié)束(因?yàn)樵跓o(wú)限for循環(huán)中)。等到尋找過(guò)程結(jié)束,ReadBytes方法會(huì)把這個(gè)過(guò)程中讀到的所有字節(jié),都返回。如果過(guò)程結(jié)束是因?yàn)槌霈F(xiàn)錯(cuò)誤,那么第二個(gè)參數(shù)的錯(cuò)誤值也會(huì)有內(nèi)容返回。
ReadBytes方法的使用示例:

package main

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

func main() {
    comment := "Go is an open source programming language that makes it easy to build simple, " +
        "reliable,  " +
        "and efficient software."
    basicReader := strings.NewReader(comment)
    reader := bufio.NewReaderSize(basicReader, 32)
    delimiter := byte(',')
    line, err := reader.ReadBytes(delimiter)
    if err != nil {
        fmt.Fprintf(os.Stderr, "EEEOR: %v\n", err)
    }
    fmt.Printf("ReadSlice讀取(%d): %q\n", len(line), line)
    fmt.Println("緩沖區(qū)里的未讀字節(jié)數(shù):", reader.Buffered())
}

另外,bufio.Reader類(lèi)型的ReadString方法完全依賴(lài)于這里的ReadBytes方法。只是在返回值的時(shí)候做了一個(gè)簡(jiǎn)單的類(lèi)型轉(zhuǎn)換,轉(zhuǎn)成了字符串類(lèi)型。具體可以看源碼:

func (b *Reader) ReadString(delim byte) (string, error) {
    bytes, err := b.ReadBytes(delim)
    return string(bytes), err
}

ReadLine方法

在bufio.Reader類(lèi)型的眾多讀取方法中,依賴(lài)ReadSlice方法的除了ReadBytes方法,還有ReadLine方法。這個(gè)方法是非常常用的一個(gè)方法,不過(guò)在讀取流程上并沒(méi)有什么特別的地方。這里就略了。

內(nèi)容泄露

最后還有一個(gè)安全性的問(wèn)題。bufio.Reader類(lèi)型的Peek方法、ReadSlice方法和ReadLine方法都有可能造成內(nèi)容泄露。主要是因?yàn)榉祷刂凳侵苯踊诰彌_區(qū)的字節(jié)切片。這個(gè)問(wèn)題在bytes包里已經(jīng)提過(guò)了:調(diào)用方可以通過(guò)這些方法返回的接口值訪問(wèn)到緩沖區(qū)的其他部分,甚至是修改緩沖區(qū)中的內(nèi)容。
在簡(jiǎn)單演示下獲取到后面的內(nèi)容,獲取之后直接就可以操作擴(kuò)張后的字節(jié)切片把里面的內(nèi)容修改掉:

package main

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

func main() {
    comment := "Test contents leak."
    basicReader := strings.NewReader(comment)
    reader := bufio.NewReaderSize(basicReader, 30)
    bytes, err := reader.Peek(5)
    if err != nil {
        fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
    }
    fmt.Printf("Peek讀取(%d): %q\n", len(bytes), bytes)

    // 擴(kuò)張返回的字節(jié)切片
    bytes = bytes[:cap(bytes)]
    fmt.Printf("利用內(nèi)容泄露獲取到了所有的內(nèi)容: %q\n", bytes)
}
向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI