首先,了解一下strings.Builder、strings.Reader和bytes.Buffer這三個數(shù)據(jù)類型中實現(xiàn)的接口。
創(chuàng)新互聯(lián)建站網(wǎng)站建設(shè)公司是一家服務(wù)多年做網(wǎng)站建設(shè)策劃設(shè)計制作的公司,為廣大用戶提供了成都網(wǎng)站設(shè)計、成都網(wǎng)站建設(shè),成都網(wǎng)站設(shè)計,廣告投放平臺,成都做網(wǎng)站選創(chuàng)新互聯(lián)建站,貼合企業(yè)需求,高性價比,滿足客戶不同層次的需求一站式服務(wù)歡迎致電。strings.Builder類型主要用于構(gòu)建字符串,它的指針類型實現(xiàn)的接口有:
strings.Reader類型主要用于讀取字符串,它的指針類型實現(xiàn)的接口有:
bytes.Buffer是集讀、寫功能于一身的數(shù)據(jù)類型,它非常適合作為字節(jié)序列的緩沖區(qū)。它的指針類型實現(xiàn)的接口非常多。
該指針類型實現(xiàn)的讀取相關(guān)的接口有:
該指針類型實現(xiàn)的寫入相關(guān)的接口有:
另外,還有一個導(dǎo)出相關(guān)的接口:fmt.Stringer
下面的代碼對公開的接口進行了驗證:
package main
import (
"bytes"
"fmt"
"io"
"strings"
)
func main() {
b1 := new(strings.Builder)
_ = interface{}(b1).(io.Writer)
_ = interface{}(b1).(io.ByteWriter)
_ = interface{}(b1).(fmt.Stringer)
b2 := strings.NewReader("")
_ = interface{}(b2).(io.Reader)
_ = interface{}(b2).(io.ReaderAt)
_ = interface{}(b2).(io.ByteReader)
_ = interface{}(b2).(io.RuneReader)
_ = interface{}(b2).(io.Seeker)
_ = interface{}(b2).(io.ByteScanner)
_ = interface{}(b2).(io.RuneScanner)
_ = interface{}(b2).(io.WriterTo)
b3 := bytes.NewBuffer([]byte{})
_ = interface{}(b3).(io.Reader)
_ = interface{}(b3).(io.ByteReader)
_ = interface{}(b3).(io.RuneReader)
_ = interface{}(b3).(io.ByteScanner)
_ = interface{}(b3).(io.RuneScanner)
_ = interface{}(b3).(io.WriterTo)
_ = interface{}(b3).(io.Writer)
_ = interface{}(b3).(io.ByteWriter)
_ = interface{}(b3).(io.ReaderFrom)
_ = interface{}(b3).(fmt.Stringer)
}
上面的這些類型實現(xiàn)了這么多的接口,目的是為了提高不同程序?qū)嶓w之間的互操作性。
在io包中,有如下幾個用于拷貝數(shù)據(jù)的函數(shù):
這幾個函數(shù)在功能上略有差別,但是首先都會接收2個參數(shù):
而這些函數(shù)的功能大致上也是把數(shù)據(jù)從src拷貝到dst。用了接口之后,不論給予參數(shù)值是什么類型的,只要實現(xiàn)了接口就行。只要實現(xiàn)了接口,這些函數(shù)幾乎就可以正常執(zhí)行了。當(dāng)然,在函數(shù)中還會對必要的參數(shù)值進行有效性的檢查,如果檢查不通過,它的執(zhí)行也是不能夠成功結(jié)束的。
來看下面的示例代碼:
package main
import (
"fmt"
"io"
"os"
"strings"
)
func main() {
src := strings.NewReader("Happy New Year")
dst := new(strings.Builder)
written, err := io.CopyN(dst, src, 5)
if err != nil {
fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
}
fmt.Println(written, dst.String())
}
首先,使用了strings.NewReader創(chuàng)建了一個字符串讀取器,并把它賦值給了變量src,然后有new了一個字符串構(gòu)建器,并將其賦予了變量dst。
之后,調(diào)用了io.CopyN函數(shù)的時候,把兩個變量的值都傳遞了進去,同時還指定了第三個參數(shù)int64類型,就是要從src中拷貝多少個字節(jié)到dst里。
雖然,變量src和dst類型分別是strings.Reader和strings.Builder,但是當(dāng)它們被傳到io.CopyN函數(shù)的時候,就已經(jīng)分別被包裝成了io.Reader類型和io.Writer類型的值。而io.CopyN函數(shù)也根本不會去在意它們的實際類型到底是什么。為了優(yōu)化的目的,io.CopyN函數(shù)中的代碼會對參數(shù)值進行再包裝,也會檢測這些參數(shù)值是否還實現(xiàn)了別的接口,甚至還會去探求某個參數(shù)值被包裝后的實際類型,是否未某個特殊的類型。但是總體上來看,這些代碼都是面向參數(shù)聲明中的接口來做的。
面向接口編程
在上面的示例中,通過面向接口編程,極大地拓展了它的適用范圍和應(yīng)用場景。換個角度來看,正式因為strings.Reader類型和strings.Builder類型都實現(xiàn)了不少接口,所以他們的值才能夠被使用在更廣闊的場景中。比如strings包和bytes包中的數(shù)據(jù)類型在實現(xiàn)了若干接口之后得到了很多好處,這就是面向接口編程帶來的優(yōu)勢。
在Go語言中,對接口的擴展是通過類型之間的嵌入來實現(xiàn)的,這也常被叫做接口的組合。這個在講接口的時候也提過,Go語言提倡使用小接口加接口組合的方式,來擴展程序的行為以及增加程序的靈活性。io代碼包恰恰就可以作為這樣的一個標(biāo)桿,它可以成為我們運用這種技巧是的一個參考標(biāo)準(zhǔn)。
以io.Reader接口為對象,來了解一下接口擴展和實現(xiàn),以及各自的功用。
在io包中,io.Reader的擴展接口有下面幾種:
然后是io包中的io.Reader接口的實現(xiàn)類型,包括以下幾項內(nèi)容:
這里忽略掉了測試源碼文件中的實現(xiàn)類型,以及不會以任何形式直接對外暴露的那些實現(xiàn)類型。
*io.LimitedReader
結(jié)構(gòu)體類型如下:
type LimitedReader struct {
R Reader // underlying reader
N int64 // max bytes remaining
}
此類型的基本類型會包裝io.Reader類型的值,并提供一個額外的受限讀取的功能。所謂的受限讀取指的是,此類型的讀取方法和Read返回的總數(shù)據(jù)量會受到限制,無論該方法被調(diào)用多少次。這個限制由該類型的字段N表明,單位是字節(jié)。
*io.SectionReader
結(jié)構(gòu)體類型如下:
type SectionReader struct {
r ReaderAt
base int64
off int64
limit int64
}
此類型的基本類型會包裝io.ReaderAt類型的值,并且會限制它的Read方法,只能夠讀取原始數(shù)據(jù)中的某一個部分,或者說一段。這個數(shù)據(jù)段的起始位置和末尾位置,需要在它被初始化的時候就指明,并且之后無法變更。該類型值的行為與切片有些類似,它只會對外暴露在其窗口之中的那些數(shù)據(jù)。
*io.teeReader
結(jié)構(gòu)體類型如下:
type teeReader struct {
r Reader
w Writer
}
func TeeReader(r Reader, w Writer) Reader {
return &teeReader{r, w}
}
此類型是一個包級私有的數(shù)據(jù)類型,也是io.TeeReader函數(shù)結(jié)果值的實際類型,這個函數(shù)接受兩個參數(shù)r和w。*teeReader的Read方法會把r中的數(shù)據(jù)經(jīng)過作為方法參數(shù)的字節(jié)切片p寫入到w??梢哉f,這是一個r和w之間的數(shù)據(jù)橋梁,而那個參數(shù)p就是這座橋上的數(shù)據(jù)搬運者。
*io.multiReader
結(jié)構(gòu)體類型如下:
type multiWriter struct {
writers []Writer
}
func MultiReader(readers ...Reader) Reader {
r := make([]Reader, len(readers))
copy(r, readers)
return &multiReader{r}
}
此類型也是一個包級私有的數(shù)據(jù)類型。通過io.MultiReader函數(shù),接受若干個io.Reader類型的參數(shù)值,返回一個實例類型為*io.multiWriter的結(jié)果值。它的Read方法被調(diào)用時,會順序的從前面的那些io.Reader類型的參數(shù)值中讀取數(shù)據(jù)。因此,也可以稱之為多對象讀取器。
*io.pipe
結(jié)構(gòu)體類型如下:
type pipe struct {
wrMu sync.Mutex // Serializes Write operations
wrCh chan []byte
rdCh chan int
once sync.Once // Protects closing done
done chan struct{}
rerr atomicError
werr atomicError
}
func Pipe() (*PipeReader, *PipeWriter) {
p := &pipe{
wrCh: make(chan []byte),
rdCh: make(chan int),
done: make(chan struct{}),
}
return &PipeReader{p}, &PipeWriter{p}
}
此類型為一個包級私有的數(shù)據(jù)類型,它比較復(fù)雜。不但實現(xiàn)了io.Reader接口,而且還實現(xiàn)了io.Writer接口。io.PipeReader類型和io.PipeWriter類型擁有的所有指針方法都是以它為基礎(chǔ)的。這些方法都只是代理了io.pipe類型值所擁有的某一個方法而已。又因為,io.Pipe函數(shù)會返回這兩個類型的指針值并分別把它們作為其生成的同步內(nèi)存管理的兩端,所以*io.pipe類型就是io包提供的同步內(nèi)存管道的核心實現(xiàn)。
*io.PipeReader
結(jié)構(gòu)體類型如下:
type PipeReader struct {
p *pipe
}
此類型可以被視為io.pipe類型的代理類型。它代理了io.pipe中一部分功能,并基于io.pipe實現(xiàn)了io.ReadCloser接口。同時,它還定義了同步內(nèi)存管道的讀取端。
上面所講的每一個類型都寫了一小段代碼,展示了這些類型的一些基本用法:
package main
import (
"fmt"
"io"
"os"
"strings"
"sync"
)
// 統(tǒng)一定義一個方法來處理錯誤,這樣不會看到很多 if err != nil {} 這種
func executeIfNoErr(err error, f func()) {
if err != nil {
fmt.Fprintf(os.Stderr, "\tERROR: %v\n", err)
return
}
f()
}
func main() {
comment := "Make the plan. " +
"Execute the plan. " +
"Expect the plan to go off the rails. " +
"Throw away the plan."
fmt.Println("原生string類型:")
reader1 := strings.NewReader(comment)
buf1 := make([]byte, 4)
n, err := reader1.Read(buf1)
var offset1, index1 int64
executeIfNoErr(err, func() {
fmt.Printf("\tRead(%d): %q\n", n, buf1[:n])
offset1 = int64(5)
index1, err = reader1.Seek(offset1, io.SeekCurrent)
})
executeIfNoErr(err, func() {
fmt.Printf("\t偏移量: %d, %d\n", offset1, index1)
n, err = reader1.Read(buf1)
})
executeIfNoErr(err, func() {
fmt.Printf("\tRead(%d): %q\n", n, buf1[:n])
})
reader1.Reset(comment)
num2 := int64(15)
fmt.Printf("LimitReader類型,限制數(shù)據(jù)量(%d):\n", num2)
reader2 := io.LimitReader(reader1, num2)
buf2 := make([]byte, 4)
for i := 0; i < 6; i++ {
n, err := reader2.Read(buf2)
executeIfNoErr(err, func() {
fmt.Printf("\tRead(%d): %q\n", n, buf2[:n])
})
}
reader1.Reset(comment)
offset3 := int64(33)
num3 := int64(37)
fmt.Printf("SectionReader類型,起始偏移量(%d),到末端的長度(%d):\n", offset3, num3)
reader3 := io.NewSectionReader(reader1, offset3, num3)
buf3 := make([]byte, 15)
for i := 0; i < 5; i++ {
n, err := reader3.Read(buf3)
executeIfNoErr(err, func() {
fmt.Printf("\tRead(%d): %q\n", n, buf3[:n])
})
}
reader1.Reset(comment)
writer4 := new(strings.Builder)
fmt.Printf("teeReader類型,write4現(xiàn)在應(yīng)該為空(%q):\n", writer4)
reader4 := io.TeeReader(reader1, writer4)
buf4 := make([]byte, 33)
for i := 0; i < 5; i++ {
n, err := reader4.Read(buf4)
executeIfNoErr(err, func() {
fmt.Printf("\tRead(%d): %q\n", n, buf4[:n])
fmt.Printf("\tWrite: %q\n", writer4)
})
}
reader5a := strings.NewReader("Make the plan.")
reader5b := strings.NewReader("Execute the plan.")
reader5c := strings.NewReader("Expect the plan to go off the rails.")
reader5d := strings.NewReader("Throw away the plan.")
fmt.Println("multiWriter類型,一共4個readers:")
reader5 := io.MultiReader(reader5a, reader5b, reader5c, reader5d)
buf5 := make([]byte, 15)
for i := 0; i < 10; i++ {
n, err := reader5.Read(buf5)
executeIfNoErr(err, func() {
fmt.Printf("\tRead(%d): %q\n", n, buf5[:n])
})
}
fmt.Println("pipe類型:")
pReader, pWriter := io.Pipe()
_ = interface{}(pReader).(io.ReadCloser) // 驗證是否實現(xiàn)了 io.ReadCloser 接口
_ = interface{}(pWriter).(io.WriteCloser)
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
n, err := pWriter.Write([]byte(comment))
defer pWriter.Close()
executeIfNoErr(err, func() {
fmt.Printf("\tWrite(%d)\n", n)
})
}()
go func() {
defer wg.Done()
buf6 := make([]byte, 15)
for i := 0; i < 10; i++ {
n, err := pReader.Read(buf6)
executeIfNoErr(err, func() {
fmt.Printf("\tRead(%d): %q\n", n, buf6[:n])
})
}
}()
wg.Wait()
fmt.Println("所有示例完成")
}
前面的內(nèi)容,主要講的是io.Reader的擴展接口和實現(xiàn)類型。當(dāng)然,io代碼包中的核心接口不止io.Reader一個。這里基于它引出的一條主線只是io包類型體系中的一部分。這里再換個角度來對io包做進一步的了解。
可以把沒有嵌入其他接口并且只定義了一個方法的接口叫做簡單接口。在io包中,這樣的接口共有11個。
另外,有的接口有著眾多的擴展接口和實現(xiàn)類型,可以稱為核心接口,io包中的核心接口只有3個:
可以把io包中的簡單接口分為四大類。這四大類接口分別針對于四種操作:讀取、寫入、關(guān)閉和讀寫位置設(shè)定。前三種操作屬于基本的I/O操作。
關(guān)于讀取操作,已經(jīng)重點講過核心接口的io.Reader。它在io包中有5個擴展接口,并有6個實現(xiàn)類型。這個包中針對讀取操作的接口還有不少。
io.ByteReader
type ByteReader interface {
ReadByte() (byte, error)
}
簡單接口,定義了一個讀取方法ReadByte。這個讀取方法能夠讀取下一個單一的字節(jié)。
RuneReader
type RuneReader interface {
ReadRune() (r rune, size int, err error)
}
簡單接口,定義了一個讀取方法ReadRune。這個讀取方法能夠讀取下一個單一的Unicode字符。
io.ByteScanner
type ByteScanner interface {
ByteReader
UnreadByte() error
}
該接口內(nèi)嵌了簡單接口io.ByteReader,并定義了額外的UnreadByte方法。它就抽象了可以讀取和讀回退單個字節(jié)的功能集。
io.RuneScanner
type RuneScanner interface {
RuneReader
UnreadRune() error
}
該接口內(nèi)嵌了簡單接口io.RuneReader,并定義了額外的UnreadRune方法。它抽象了可以讀取和讀回退單個Unicode字符的功能集。
io.ReaderAt
type ReaderAt interface {
ReadAt(p []byte, off int64) (n int, err error)
}
簡單接口,定義了一個ReadAt方法。這是一個純粹的只讀方法,它只去讀取其所屬值中包含的字節(jié),而不對這個值進行任何改動。比如,它絕對不能去修改已讀計數(shù)的值。這也是io.ReaderAt接口與其實現(xiàn)類型之間最重要的一個約定。因此,如果僅僅并發(fā)的調(diào)用某一個值的ReadAt方法,那么安全性應(yīng)該是可以得到保障的。
io.WriterTo
type WriterTo interface {
WriteTo(w Writer) (n int64, err error)
}
簡單接口,定義了一個WriteTo的讀取方法。該方法接受一個io.Writer類型的參數(shù)值,會把其所屬值中的數(shù)據(jù)讀出,并寫入到這個參數(shù)值中。
io.ReaderFrom
type ReaderFrom interface {
ReadFrom(r Reader) (n int64, err error)
}
簡單接口,定義了一個ReadFrom的寫入方法。該方法接受一個io.Reader類型的參數(shù)值,會從該參數(shù)值中讀取出數(shù)據(jù),并寫入到其所屬值中。
從上面這些接口中,可以看出,在io包中與寫入操作有關(guān)的接口都與讀取操作相關(guān)的接口有著一定的對應(yīng)關(guān)系。下面就說是寫入操作有關(guān)的接口。
io.Write是核心接口?;谒臄U展接口如下:
這兩個是寫入操作相關(guān)的簡單接口。在io包中,沒有他們的實現(xiàn)類型。
順便提一下這個數(shù)據(jù)類型:*io.File。這個類型不但是io.WriterAt接口的實現(xiàn)類型,同時還實現(xiàn)了io.ReadWriteCloser接口和io.ReadWriteSeeker接口。就是說,該類型支持的I/O操作非常豐富。
這個接口是一個讀寫位置設(shè)定相關(guān)的簡單接口,也僅僅定義了一個Seek方法。該方法主要用于尋找并設(shè)定下一次讀取或?qū)懭霑r的起始索引位置,在strings包里講過。
在io包中,有幾個基于io.Seeker的擴展接口:
這兩個類型的指針:strings.Reader和io.SectionReader,都實現(xiàn)了io.Seeker接口。順便提一下,這兩個類型的指針也都是io.ReaderAt接口的實現(xiàn)類型。
這是關(guān)閉操作相關(guān)的接口,非常通用,它的擴展接口和實現(xiàn)類型都不少。單從名稱上就能看出io包中哪些接口是它的擴展接口。它的實現(xiàn)類型,在io包中只有io.PipeReader和io.PipeWriter。
本篇是為了能夠使我們牢記io包中有著網(wǎng)狀關(guān)系的接口和數(shù)據(jù)類型。如果暫時未能牢記,至少可以作為深刻記憶它們的開始。
在之后需要思考和時間的是:在什么時候應(yīng)該編寫哪些數(shù)據(jù)類型實現(xiàn)io包中的哪些接口,并以此得到大的好處。
創(chuàng)新互聯(lián)www.cdcxhl.cn,專業(yè)提供香港、美國云服務(wù)器,動態(tài)BGP最優(yōu)骨干路由自動選擇,持續(xù)穩(wěn)定高效的網(wǎng)絡(luò)助力業(yè)務(wù)部署。公司持有工信部辦法的idc、isp許可證, 機房獨有T級流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確進行流量調(diào)度,確保服務(wù)器高可用性。佳節(jié)活動現(xiàn)已開啟,新人活動云服務(wù)器買多久送多久。