Go 語言 bytes-Buffer 源碼詳解 2

前言

前面一篇文章 Go 語言 bytes.Buffer 源碼詳解之 1,我們學習了 bytes.Buffer 的結構和基礎方法,瞭解了緩衝區的運行機制,其中最重要的是要理解整個結構被分爲了三段:已讀數據、未讀數據、空閒部分,忘記的小夥伴再複習下哦。緩衝區的存在,就是爲讀寫服務的,那麼本篇文章我們就一起來學習下讀寫方法是如何利用緩衝區實現的吧!

Buffer 結構示意圖

源碼分析

Write()

Write 方法將字節切片 p 中的數據寫入到 緩衝切片中,返回寫入的字節長度和產生的 error

由於 Write 調用了 grow 方法,如果底層的緩衝切片太大無法重新分配,會產生 ErrTooLarge 的 panic

func (b *Buffer) Write([]byte) (n int, err error) {
 b.lastRead = opInvalid

 // 通過調用 tryGrowByReslice 和 grow 兩個方法,確保底層的緩衝切片的長度可以寫入len(p)個字節。
 m, ok := b.tryGrowByReslice(len(p))
 if !ok {
  m = b.grow(len(p))
 }

 // 走到這裏,說明 buf 從m位置開始已經有了 len(p)個空閒字節,調用copy方法,將p中的數據複製過去
 return copy(b.buf[m:], p), nil
}

WriteString()

WriteString 和 Write 方法類似,將傳入的字符串 s 寫入到底層的緩衝切片 buf 中,返回成功寫入的字節數 n 和產生的 error

func (b *Buffer) WriteString(s string) (n int, err error) {
 b.lastRead = opInvalid

 // 通過調用 tryGrowByReslice 和 grow 兩個方法,確保底層的緩衝切片的長度可以寫入 len(s) 個字節。
 m, ok := b.tryGrowByReslice(len(s))
 if !ok {
  m = b.grow(len(s))
 }
 // 到這裏說明buf 從m位置開始已經有了 len(s) 個空閒字節,調用copy方法,將 s 複製到底層緩衝切片中
 return copy(b.buf[m:], s), nil
}

WriteByte()

和 Write 方法類似,寫入單個字節,而非字節切片

func (b *Buffer) WriteByte(c byte) error {
 b.lastRead = opInvalid

 // 通過調用 tryGrowByReslice 和 grow 兩個方法,確保底層的緩衝切片的長度可以寫入 1 個字節
 m, ok := b.tryGrowByReslice(1)
 if !ok {
  m = b.grow(1)
 }
 // m 表示擴容後寫入的開始位置,直接賦值爲要寫入的字節
 b.buf[m] = c
 return nil
}

WriteRune()

和 Write 方法類似,區別是寫入 rune,而非字節切片

func (b *Buffer) WriteRune(r rune) (n int, err error) {

 // 如果r < utf8.RuneSelf,說明 r 就是一個字節,那麼直接調用 WriteByte 方法
 if r < utf8.RuneSelf {
  b.WriteByte(byte(r))
  return 1, nil
 }
 b.lastRead = opInvalid

 // 通過調用 tryGrowByReslice 和 grow 兩個方法,確保底層的緩衝切片的長度可以寫入 utf8.UTFMax 個字節
 m, ok := b.tryGrowByReslice(utf8.UTFMax)
 if !ok {
  m = b.grow(utf8.UTFMax)
 }
  
  // 此時的buf 長度,變成了 len(buf)+ utf8.UTFMax,utf8.UTFMax 是rune 可能的最大長度,但是當前 rune 的大小可能小於這個值
 // 調用 utf8.EncodeRune() 方法,將 rune r 寫入到 buf 中,返回寫入的字節數
 n = utf8.EncodeRune(b.buf[m:m+utf8.UTFMax], r)

 // 更新 buf 的長度,因爲 n<= utf8.UTFMax
 b.buf = b.buf[:m+n]
 return n, nil
}

ReadFrom()

// 緩衝切片留出的最小空閒空間
// ReadFrom 方法會用到該參數,即從一個 Reader 寫入數據到底層緩衝切片 buf 時,buf 留出的最小空閒空間
const MinRead = 512


func (b *Buffer) ReadFrom(r io.Reader) (n int64, err error) {
 b.lastRead = opInvalid

 // for 循環,不斷讀取寫入數據,直至遇到Reader 讀取數據完畢產生 EOF error 或者 其他 error
 for {
    
  // 保證至少留出 MinRead 個空閒字節空間,並返回寫入開始位置 i
  i := b.grow(MinRead)

  // grow 方法將長度變爲了 i+MinRead,改回來
  b.buf = b.buf[:i]

  // i 位置開始,到 cap(buf) 結束的空間,即從 i位置 開始的底層數組所有空間都供 reader r 讀取數據寫入
  // Read 方法會返回讀取的字節數和產生的error,根據Read方法的定義,應該先處理m,再處理e
  m, e := r.Read(b.buf[i:cap(b.buf)])
  if m < 0 {
   panic(errNegativeRead)
  }

  // 如果 m 大於等於0,說明讀取並寫入數據到buf 中了,修改buf 的長度
  b.buf = b.buf[:i+m]
  // 已讀字節數n 加 m
  n += int64(m)

  // 如果Reader r 讀取過程中遇到了EOF error,說明讀取數據完畢了,返回 error=nil
  if e == io.EOF {
   return n, nil // e is EOF, so return nil explicitly
  }

  // 遇到了其他error,返回error
  if e != nil {
   return n, e
  }
 }
}

上面介紹的是寫入緩衝區的相關操作,接下來我們來看讀取相關的操作。

WriteTo()

WriteTo 方法,讀取字節緩衝切片中的數據,交由  Writer w 去消費使用,最終返回 Writer w 消費的字節量和產生的 error

func (b *Buffer) WriteTo(w io.Writer) (n int64, err error) {
 b.lastRead = opInvalid

 // nBytes:未讀數據的長度
 if nBytes := b.Len(); nBytes > 0 {
  // 如果未讀數據的長度大於0,將從已讀計數 off 到len()部分的未讀數據,寫入到Writer w 中
  // Write 返回消費的字節數,以及產生的error
  m, e := w.Write(b.buf[b.off:])
  // 如果消費的長度,大於可用長度,不符合邏輯,panic
  if m > nBytes {
   panic("bytes.Buffer.WriteTo: invalid Write count")
  }
  // 被消費了 m 個字節,已讀計數相應增加 m
  b.off += m

  // 消費的字節量 n = int64(m)
  n = int64(m)

  // 如果產生了 error,返回
  if e != nil {
   return n, e
  }

  // 根據 io.Writer 接口對 Write 方法的定義,如果寫入的數量 m != nBytes,一定會返回error!=nil
  // 因此上一步的 e!=nil 一定成立,會直接返回,導致到不了這一步,這一步相當於做了個double check
  if m != nBytes {
   return n, io.ErrShortWrite
  }
 }

 // 到這一步,說明緩衝切片中的未讀數據被讀完了,直接調用Reset()方法重置
 b.Reset()
 return n, nil
}

Read()

Read 方法,讀取底層緩衝字節切片 buf 中的數據,寫入到字節切片  p  中

// 方法讀取的字節數,和產生的 error
func (b *Buffer) Read([]byte) (n int, err error) {
 b.lastRead = opInvalid

  // 如果 buf 中無數據可讀,且len(p)=0,返回 error=nil,否則返回 error=EOF
 // 如果未讀數據部分爲空,沒有數據可讀
 if b.empty() {
  // 首先將字節緩衝切片重置
  b.Reset()
  // len(p)=0,返回的 error=nil
  if len(p) == 0 {
   return 0, nil
  }
  // 讀取的數據小於 len(p),返回 EOF error
  return 0, io.EOF
 }

 // 存在未讀數據,調用 copy 方法,從 off 位置開始複製數據到 p 中,返回複製的字節數
 n = copy(p, b.buf[b.off:])

 // 更新已讀計數
 b.off += n

 // 讀取到數據了,此次是一次合法的讀操作,更新 lastRead 爲 opRead
 if n > 0 {
  b.lastRead = opRead
 }
 // 返回讀取的字節數 n,error=nil
 return n, nil
}

Next()

Next 方法返回未讀數據的前 n 個字節,如果未讀數據長度小於 n 個字節,那麼就返回所有的未讀數據。由於方法返回的數據是基於 buf 的切片,存在數據泄露的風險,且數據的有效期在下次調用 read 或 write 方法前,因爲調用 read、write 方法會修改底層數據。

func (b *Buffer) Next(n int) []byte {
 b.lastRead = opInvalid

 // m:未讀數據長度
 m := b.Len()

 // 如果需要的字節數 n,大於未讀數據長度m,那麼n=m
 if n > m {
  n = m
 }

 // 賦值 data 爲所需的n個字節
 data := b.buf[b.off : b.off+n]

 // 已讀計數增加 n
 b.off += n
 if n > 0 {
  b.lastRead = opRead
 }
 return data
}

ReadByte()

類似 Read 方法,ReadByte 讀取一個字節,返回讀取的字節和產生的 error

func (b *Buffer) ReadByte() (byte, error) {

 // 如果沒有未讀數據,重置,返回 EOF error
 if b.empty() {
  // Buffer is empty, reset to recover space.
  b.Reset()
  return 0, io.EOF
 }

 // 讀取一個字節,然後修改已讀計數
 c := b.buf[b.off]
 b.off++
 b.lastRead = opRead
 return c, nil
}

ReadRune()

類似 Read 方法,ReadRune 讀取一個 utf-8 編碼的 rune,返回 rune 的值、大小以及產生的 error

func (b *Buffer) ReadRune() (r rune, size int, err error) {
 // 如果沒有數據可讀,重置,返回 EOF error
 if b.empty() {
  b.Reset()
  return 0, 0, io.EOF
 }

 // c 代表開始讀取的第一個字節
 c := b.buf[b.off]
 // 如果 c < utf8.RuneSelf,表示 c 是一個單字節的 rune,直接返回這個 rune,已讀計數加一即可
 if c < utf8.RuneSelf {
  b.off++
  b.lastRead = opReadRune1
  return rune(c), 1, nil
 }

 // 從已讀計數位置開始,調用utf8.DecodeRune,方法返回從已讀計數位置開始的rune,以及對應的字節數
 r, n := utf8.DecodeRune(b.buf[b.off:])

 // 修改已讀計數
 b.off += n

 // 修改lastRead
 b.lastRead = readOp(n)
 return r, n, nil
}

UnreadRune()

回退一個 rune,只能在 ReadRune 後調用該方法纔有效,其他 read 方法之後後調用該方法非法,因爲其他相關的 read 方法記錄的 lastRead = opRead,而不是 opReadRune*

func (b *Buffer) UnreadRune() error {

 // lastRead <= opInvalid,表示上一次調用爲非ReadRune 方法,不能進行回退
 if b.lastRead <= opInvalid {
  return errors.New("bytes.Buffer: UnreadRune: previous operation was not a successful ReadRune")
 }
 // 回退
 if b.off >= int(b.lastRead) {
  b.off -= int(b.lastRead)
 }
 // 只能回退一次,不能再次回退
 b.lastRead = opInvalid
 return nil
}

UnreadByte()

回退一個字節,該方法的要求比 UnreadRune 方法要低,只要是 read 相關的方法都能回退一個字節

var errUnreadByte = errors.New("bytes.Buffer: UnreadByte: previous operation was not a successful read")

func (b *Buffer) UnreadByte() error {
 // 只有 lastRead == opInvalid 纔不能回退( ReadRune 也可以回退 )
 if b.lastRead == opInvalid {
  return errUnreadByte
 }

 // 只能回退一次
 b.lastRead = opInvalid

 // 已讀計數減一
 if b.off > 0 {
  b.off--
 }
 return nil
}

readSlice()

私有方法,readSlice 讀取未讀數據,直至找到 delim 這個字符停止,然後返回遍歷到的數據。返回的數據是基於底層緩衝切片的引用,存在數據泄露的風險。

func (b *Buffer) readSlice(delim byte) (line []byte, err error) {

 // 調用 IndexByte(),從 off 位置開始查找,找到第一個出現 delim 的索引,如果沒找到會返回 -1
 i := IndexByte(b.buf[b.off:], delim)

 // 因爲上一步索引從0開始,所以要再加 1
 end := b.off + i + 1

 // 沒有找到,需要返回所有未讀數據,因此 end 賦值爲 緩衝數組長度,err 爲 EOF
 if i < 0 {
  end = len(b.buf)
  err = io.EOF
 }

 // 返回遍歷過的數據,更新已讀計數
 line = b.buf[b.off:end]
 b.off = end

 // 此次操作也是 opRead
 b.lastRead = opRead

 // 返回數據和error
 return line, err
}

ReadBytes()

ReadBytes 遍歷未讀數據,直至遇到一個字節值爲 delim 的分隔符,然後返回遍歷過的數據(包括該分隔符)和產生的 error。

ReadBytes 直接調用的 readSlice,因此只有一種情況會返回 error!=nil,即在未讀數據中,遍歷完所有數據但沒有找到該分隔符時,此時會返回所有未讀數據和 EOF error。

func (b *Buffer) ReadBytes(delim byte) (line []byte, err error) {
 // 直接調用 readSlice() 方法,但是該方法返回的是底層切片的引用
 // readSlice() 方法已經修改了 已讀計數和 lastRead
 slice, err := b.readSlice(delim)

 // 由於readSlice() 返回的是引用,數據可能因爲其他方法調用被修改,因此拷貝一份數據
 line = append(line, slice...)
 return line, err
}

ReadString()

ReadString 和 ReadBytes 類似,區別是該方法返回的是字符串形式。

func (b *Buffer) ReadString(delim byte) (line string, err error) {
 // 直接調用 readSlice()方法,但是該方法返回的是底層切片的引用
 // readSlice() 方法已經修改了 已讀計數和 lastRead
 slice, err := b.readSlice(delim)
 // 轉爲字符串返回
 return string(slice), err
}

NewBuffer()

func NewBuffer(buf []byte) *Buffer { return &Buffer{buf: buf} }

NewBufferString()

func NewBufferString(s string) *Buffer {
 return &Buffer{buf: []byte(s)}
}

使用示例

下面的示例,使用了 bytes.Buffer 作爲緩衝區,完成了一個文件複製的操作。

func main() {
 var buffer bytes.Buffer
 srcFile, _ := os.OpenFile("test.txt", os.O_RDWR, 0666)
 n, err := buffer.ReadFrom(srcFile)
 fmt.Println(n, err)                     // 303190 <nil>
 fmt.Println(buffer.Len(), buffer.Cap()) // 303190 523776

 targetFile, _ := os.OpenFile("target.txt", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
 n, err = buffer.WriteTo(targetFile)
 fmt.Println(n, err)                // 303190 <nil>
 fmt.Println(buffer.Len(), buffer.Cap()) // 0 523776
}

總結

本篇文章我們學習了 bytes.Buffer 中讀寫相關的源碼實現。針對寫操作,都會先確保有足夠的可用空間,然後再將數據複製到緩衝區的未讀數據部分;針對讀操作,就是將未讀部分的數據拷貝出去,然後更新已讀計數。

到這裏我們就把 bytes.Buffer 源碼給過完了,俗話說知己知彼,百戰不殆,瞭解了 bytes.Buffer 的原理後,相信你之後使用起來應該會更得心應手!

更多

個人博客: https://lifelmy.github.io/

微信公衆號:漫漫 Coding 路

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/C5IV5b2jMIenhyz25E-wFw