?一般的都伪,計(jì)算機(jī)程序是:輸入 (Input) 經(jīng)過算法處理產(chǎn)生輸出 (Output)。各種語言一般都會(huì)提供IO庫供開發(fā)者使用。Go語言也不例外让网。
?Go 語言中,為了方便開發(fā)者使用型将,將 IO 操作封裝在了如下幾個(gè)包中:
-io 為 IO 原語(I/O primitives)提供基本的接口
-io/ioutil 封裝一些實(shí)用的 I/O 函數(shù)
-fmt 實(shí)現(xiàn)格式化 I/O寂祥,類似 C 語言中的 printf 和 scanf
-bufio 實(shí)現(xiàn)帶緩沖I/O
?這里會(huì)詳細(xì)介紹這些 IO 包提供的函數(shù)、類型和方法七兜,同時(shí)通過實(shí)例講解這些包的使用方法。
基本的 IO 接口
?io 包為 I/O 原語提供了基本的接口福扬。它主要包裝了這些原語的已有實(shí)現(xiàn)腕铸。由于這些被接口包裝的I/O原語是由不同的低級(jí)操作實(shí)現(xiàn),因此铛碑,在另有聲明之前不該假定它們的并行執(zhí)行是安全的狠裹。
?在 io 包中最重要的兩個(gè)接口是io.go文件中:Reader 和 Writer 接口。本章所提到的各種 IO 包汽烦,都跟這兩個(gè)接口有關(guān)涛菠,也就是說,只要滿足這兩個(gè)接口,它就可以使用 IO 包的功能俗冻。
Reader接口:
/*
Read 將 len(p) 個(gè)字節(jié)讀取到 p 中礁叔。它返回讀取的字節(jié)數(shù) n(0 <= n <= len(p)) 以及任何遇到的錯(cuò)誤。
即使 Read 返回的 n < len(p)迄薄,它也會(huì)在調(diào)用過程中占用 len(p) 個(gè)字節(jié)作為暫存空間琅关。若可讀取的數(shù)據(jù)不到 len(p) 個(gè)字節(jié),
Read 會(huì)返回可用數(shù)據(jù)讥蔽,而不是等待更多數(shù)據(jù)涣易。
當(dāng) Read 在成功讀取 n > 0 個(gè)字節(jié)后遇到一個(gè)錯(cuò)誤或 EOF (end-of-file),它會(huì)返回讀取的字節(jié)數(shù)冶伞。
它可能會(huì)同時(shí)在本次的調(diào)用中返回一個(gè)non-nil錯(cuò)誤,或在下一次的調(diào)用中返回這個(gè)錯(cuò)誤(且 n 為 0)新症。
一般情況下, Reader會(huì)返回一個(gè)非0字節(jié)數(shù)n, 若 n = len(p) 個(gè)字節(jié)從輸入源的結(jié)尾處由 Read 返回,Read可能返回 err == EOF 或者 err == nil响禽。并且之后的 Read() 都應(yīng)該返回 (n:0, err:EOF)账劲。
調(diào)用者在考慮錯(cuò)誤之前應(yīng)當(dāng)首先處理返回的數(shù)據(jù)。這樣做可以正確地處理在讀取一些字節(jié)后產(chǎn)生的 I/O 錯(cuò)誤金抡,同時(shí)允許EOF的出現(xiàn)瀑焦。
根據(jù) Go 語言中關(guān)于接口和滿足了接口的類型的定義,我們知道 Reader 接口的方法集只包含一個(gè) Read 方法,因此梗肝,所有實(shí)現(xiàn)了 Read 方法的類型都滿足 io.Reader 接口榛瓮,也就是說,在所有需要 io.Reader 的地方巫击,可以傳遞實(shí)現(xiàn)了 Read() 方法的類型的實(shí)例禀晓。
*/
type Reader interface {
Read(p []byte) (n int, err error)
}
下面,我們通過具體例子來談?wù)勗摻涌诘挠梅ā?/p>
//ReadFrom 函數(shù)將 io.Reader 作為參數(shù)坝锰,也就是說粹懒,ReadFrom 可以從任意的地方讀取數(shù)據(jù),只要來源實(shí)現(xiàn)了 io.Reader 接口顷级。比如凫乖,我們可以從標(biāo)準(zhǔn)輸入、文件弓颈、字符串等讀取數(shù)據(jù)帽芽,示例代碼如下:
func ReadFrom(reader io.Reader, num int) ([]byte, error) {
p := make([]byte, num)
n, err := reader.Read(p)
if n > 0 {
return p[:n], nil
}
return p, err
}
// 從標(biāo)準(zhǔn)輸入讀取
data, err = ReadFrom(os.Stdin, 11)
// 從普通文件讀取,其中 file 是 os.File 的實(shí)例
data, err = ReadFrom(file, 9)
// 從字符串讀取
data, err = ReadFrom(strings.NewReader("from string"), 12)
小貼士
?io.EOF 變量的定義:var EOF = errors.New("EOF")翔冀,是 error 類型导街。根據(jù) reader 接口的說明,在 n > 0 且數(shù)據(jù)被讀完了的情況下纤子,當(dāng)次返回的 error 有可能是 EOF 也有可能是 nil搬瑰。
Writer 接口
/*
Write 將 len(p) 個(gè)字節(jié)從 p 中寫入到基本數(shù)據(jù)流中款票。
它返回從 p 中被寫入的字節(jié)數(shù) n(0 <= n <= len(p))以及任何遇到的引起寫入提前停止的錯(cuò)誤。
若 Write 返回的 n < len(p)泽论,它就必須返回一個(gè) 非nil 的錯(cuò)誤艾少。
*/
type Writer interface {
Write(p []byte) (n int, err error)
}
?同樣的,所有實(shí)現(xiàn)了Write方法的類型都實(shí)現(xiàn)了 io.Writer 接口佩厚。
?在上個(gè)例子中姆钉,我們是自己實(shí)現(xiàn)一個(gè)函數(shù)接收一個(gè) io.Reader 類型的參數(shù)。這里抄瓦,我們通過標(biāo)準(zhǔn)庫的例子來學(xué)習(xí)潮瓶。
?在fmt標(biāo)準(zhǔn)庫print.go文件中,有一組函數(shù):Fprint/Fprintf/Fprintln钙姊,它們接收一個(gè) io.Wrtier 類型參數(shù)(第一個(gè)參數(shù))毯辅,也就是說它們將數(shù)據(jù)格式化輸出到 io.Writer 中。那么煞额,調(diào)用這組函數(shù)時(shí)思恐,該如何傳遞這個(gè)參數(shù)呢?
?我們以 fmt.Fprintln 為例膊毁,同時(shí)看一下 fmt.Println 函數(shù)的源碼胀莹。
func Println(a ...interface{}) (n int, err error) {
return Fprintln(os.Stdout, a...)
}
?很顯然,fmt.Println會(huì)將內(nèi)容輸出到標(biāo)準(zhǔn)輸出中
實(shí)現(xiàn)了 io.Reader 接口或 io.Writer 接口的類型
?初學(xué)者看到函數(shù)參數(shù)是一個(gè)接口類型婚温,很多時(shí)候有些束手無策描焰,不知道該怎么傳遞參數(shù)。還有人問:標(biāo)準(zhǔn)庫中有哪些類型實(shí)現(xiàn)了 io.Reader 或 io.Writer 接口栅螟?
?通過本文上面的例子荆秦,我們可以知道,os.File 同時(shí)實(shí)現(xiàn)了這兩個(gè)接口力图。我們還看到 os.Stdin/Stdout 這樣的代碼步绸,它們似乎分別實(shí)現(xiàn)了 io.Reader/io.Writer 接口。沒錯(cuò)吃媒,實(shí)際上在 os 包中有這樣的代碼:
var (
Stdin = NewFile(uintptr(syscall.Stdin), "/dev/stdin")
Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")
Stderr = NewFile(uintptr(syscall.Stderr), "/dev/stderr")
)
?也就是說瓤介,Stdin/Stdout/Stderr 只是三個(gè)特殊的文件類型的標(biāo)識(shí)(即都是 os.File 的實(shí)例),自然也實(shí)現(xiàn)了 io.Reader 和 io.Writer晓折。
?列出實(shí)現(xiàn)了 io.Reader 或 io.Writer 接口的類型(導(dǎo)出的類型):(注:godoc 命令支持額外參數(shù) -analysis 惑朦,能列出都有哪些類型實(shí)現(xiàn)了某個(gè)接口,相關(guān)參考 godoc -h
漓概。
-os.File 同時(shí)實(shí)現(xiàn)了 io.Reader 和 io.Writer
-strings.Reader 實(shí)現(xiàn)了 io.Reader
-bufio.Reader/Writer 分別實(shí)現(xiàn)了 io.Reader 和 io.Writer
-bytes.Buffer 同時(shí)實(shí)現(xiàn)了 io.Reader 和 io.Writer
-bytes.Reader 實(shí)現(xiàn)了 io.Reader
-compress/gzip.Reader/Writer 分別實(shí)現(xiàn)了 io.Reader 和 io.Writer
-crypto/cipher.StreamReader/StreamWriter 分別實(shí)現(xiàn)了 io.Reader 和 io.Writer
-crypto/tls.Conn 同時(shí)實(shí)現(xiàn)了 io.Reader 和 io.Writer
-encoding/csv.Reader/Writer 分別實(shí)現(xiàn)了 io.Reader 和 io.Writer
-mime/multipart.Part 實(shí)現(xiàn)了 io.Reader
-net/conn 分別實(shí)現(xiàn)了 io.Reader 和 io.Writer(Conn接口定義了Read/Write)
除此之外,io 包本身也有這兩個(gè)接口的實(shí)現(xiàn)類型病梢。如:
實(shí)現(xiàn)了 Reader 的類型:LimitedReader胃珍、PipeReader梁肿、SectionReader
實(shí)現(xiàn)了 Writer 的類型:PipeWriter
ReaderAt 和 WriterAt 接口
/*
ReadAt 從基本輸入源的偏移量 off 處開始,將 len(p) 個(gè)字節(jié)讀取到 p 中觅彰。它返回讀取的字節(jié)數(shù) n(0 <= n <= len(p))以及任何遇到的錯(cuò)誤吩蔑。
當(dāng) ReadAt 返回的 n < len(p) 時(shí),它就會(huì)返回一個(gè) 非nil 的錯(cuò)誤來解釋 為什么沒有返回更多的字節(jié)填抬。在這一點(diǎn)上烛芬,ReadAt 比 Read 更嚴(yán)格。
即使 ReadAt 返回的 n < len(p)飒责,它也會(huì)在調(diào)用過程中使用 p 的全部作為暫存空間赘娄。若可讀取的數(shù)據(jù)不到 len(p) 字節(jié),ReadAt 就會(huì)阻塞,直到所有數(shù)據(jù)都可用或一個(gè)錯(cuò)誤發(fā)生宏蛉。 在這一點(diǎn)上 ReadAt 不同于 Read遣臼。
若 n = len(p) 個(gè)字節(jié)從輸入源的結(jié)尾處由 ReadAt 返回,Read可能返回 err == EOF 或者 err == nil
若 ReadAt 攜帶一個(gè)偏移量從輸入源讀取拾并,ReadAt 應(yīng)當(dāng)既不影響偏移量也不被它所影響揍堰。
可對(duì)相同的輸入源并行執(zhí)行 ReadAt 調(diào)用。
*/
type ReaderAt interface {
ReadAt(p []byte, off int64) (n int, err error)
}
可見嗅义,ReaderAt 接口使得可以從指定偏移量處開始讀取數(shù)據(jù)屏歹。
簡(jiǎn)單示例代碼如下:
reader := strings.NewReader("一起學(xué)習(xí)Go語言")
p := make([]byte, 6)
n, err := reader.ReadAt(p, 3)
if err != nil {
fmt.Println(err.Error())
}
fmt.Printf("%s, %d\n", p, n)
//輸出: 起學(xué), 6
WriterAt 接口的定義如下:
/*
WriteAt 從 p 中將 len(p) 個(gè)字節(jié)寫入到偏移量 off 處的基本數(shù)據(jù)流中。它返回從 p 中被寫入的字節(jié)數(shù) n(0 <= n <= len(p))以及任何遇到的引起寫入提前停止的錯(cuò)誤之碗。若 WriteAt 返回的 n < len(p)蝙眶,它就必須返回一個(gè) 非nil 的錯(cuò)誤。
若 WriteAt 攜帶一個(gè)偏移量寫入到目標(biāo)中继控,WriteAt 應(yīng)當(dāng)既不影響偏移量也不被它所影響械馆。
若被寫區(qū)域沒有重疊,可對(duì)相同的目標(biāo)并行執(zhí)行 WriteAt 調(diào)用武通。
*/
type WriterAt interface {
WriteAt(p []byte, off int64) (n int, err error)
}
我們可以通過該接口將數(shù)據(jù)寫入到數(shù)據(jù)流的特定偏移量之后霹崎。
通過簡(jiǎn)單示例來演示 WriteAt 方法的使用(os.File 實(shí)現(xiàn)了 WriterAt 接口):
file, err := os.Create("write_at.txt")
if err != nil {
fmt.Println(err.Error())
return
}
defer func() {
err = file.Close()
if err != nil {
fmt.Println(err.Error())
}
}()
_, err = file.WriteString("一起學(xué)習(xí)Go語言,")
if err != nil {
fmt.Println(err.Error())
return
}
n, err := file.WriteAt([]byte("用我的方式"), 27)
if err != nil {
fmt.Println(err.Error())
return
}
fmt.Println(n)
//會(huì)在當(dāng)前目錄生成write_at.txt文件, 打開文件 write_at.txt,內(nèi)容是:一起學(xué)習(xí)Go語言,用我的方式
ReaderFrom 和 WriterTo 接口
ReaderFrom 的定義如下:
/*
ReadFrom 從 r 中讀取數(shù)據(jù)冶忱,直到 EOF 或發(fā)生錯(cuò)誤尾菇。其返回值 n 為讀取的字節(jié)數(shù)。除 io.EOF 之外囚枪,在讀取過程中遇到的任何錯(cuò)誤也將被返回派诬。
如果 ReaderFrom 可用,Copy 函數(shù)就會(huì)使用它链沼。
*/
type ReaderFrom interface {
ReadFrom(r Reader) (n int64, err error)
}
注意:ReadFrom 方法不會(huì)返回 err == EOF默赂。
下面的例子簡(jiǎn)單的實(shí)現(xiàn)將文件中的數(shù)據(jù)全部讀取(顯示在標(biāo)準(zhǔn)輸出):
file, err := os.Open("./write_at.txt")
if err != nil {
fmt.Println(err.Error())
return
}
defer func() {
err = file.Close()
if err != nil {
fmt.Println(err.Error())
}
}()
writer := bufio.NewWriter(os.Stdin)
n, err := writer.ReadFrom(file)
if err != nil {
fmt.Println(err.Error(), n)
return
}
err = writer.Flush()
if err != nil {
fmt.Println(err.Error())
return
}
//輸出:一起學(xué)習(xí)Go語言,用我的方式%
?當(dāng)然括勺,我們可以通過 ioutil 包的 ReadFile 函數(shù)獲取文件全部?jī)?nèi)容缆八。其實(shí)曲掰,跟蹤一下 ioutil.ReadFile 的源碼,會(huì)發(fā)現(xiàn)其實(shí)也是通過 ReadFrom 方法實(shí)現(xiàn)(用的是 bytes.Buffer奈辰,它實(shí)現(xiàn)了 ReaderFrom 接口)栏妖。
?如果不通過 ReadFrom 接口來做這件事,而是使用 io.Reader 接口奖恰,我們有兩種思路:
1.先獲取文件的大械踔骸(File 的 Stat 方法),之后定義一個(gè)該大小的 []byte瑟啃,通過 Read 一次性讀取
2.定義一個(gè)小的 []byte论泛,不斷的調(diào)用 Read 方法直到遇到 EOF,將所有讀取到的 []byte 連接到一起
這里不給出實(shí)現(xiàn)代碼了翰守,有興趣的可以實(shí)現(xiàn)一下孵奶。
提示
通過查看 bufio.Writer 或 strings.Buffer 類型的 ReadFrom 方法實(shí)現(xiàn),會(huì)發(fā)現(xiàn)蜡峰,其實(shí)它們的實(shí)現(xiàn)和上面說的第 2 種思路類似了袁。
WriterTo的定義如下:
/*
WriteTo 將數(shù)據(jù)寫入 w 中,直到?jīng)]有數(shù)據(jù)可寫或發(fā)生錯(cuò)誤湿颅。其返回值 n 為寫入的字節(jié)數(shù)载绿。 在寫入過程中遇到的任何錯(cuò)誤也將被返回。
如果 WriterTo 可用油航,Copy 函數(shù)就會(huì)使用它崭庸。
*/
type WriterTo interface {
WriteTo(w Writer) (n int64, err error)
}
讀者是否發(fā)現(xiàn),其實(shí) ReaderFrom 和 WriterTo 接口的方法接收的參數(shù)是 io.Reader 和 io.Writer 類型谊囚。根據(jù) io.Reader 和 io.Writer 接口的講解怕享,對(duì)該接口的使用應(yīng)該可以很好的掌握。
這里只提供簡(jiǎn)單的一個(gè)示例代碼:將一段文本輸出到標(biāo)準(zhǔn)輸出
reader := bytes.NewReader([]byte("Go語言中文網(wǎng)"))
reader.WriteTo(os.Stdout)
通過 io.ReaderFrom 和 io.WriterTo 的學(xué)習(xí)镰踏,我們知道函筋,如果這樣的需求,可以考慮使用這兩個(gè)接口:“一次性從某個(gè)地方讀或?qū)懙侥硞€(gè)地方去奠伪〉剩”
Seeker 接口
接口定義如下:
type Seeker interface {
Seek(offset int64, whence int) (ret int64, err error)
}
官方文檔中關(guān)于該接口方法的說明:
Seek 設(shè)置下一次 Read 或 Write 的偏移量為 offset,它的解釋取決于 whence: 0 表示相對(duì)于文件的起始處绊率,1 表示相對(duì)于當(dāng)前的偏移谨敛,而 2 表示相對(duì)于其結(jié)尾處。 Seek 返回新的偏移量和一個(gè)錯(cuò)誤滤否,如果有的話脸狸。
?也就是說,Seek 方法是用于設(shè)置偏移量的藐俺,這樣可以從某個(gè)特定位置開始操作數(shù)據(jù)流肥惭。聽起來和 ReaderAt/WriteAt 接口有些類似盯仪,不過 Seeker 接口更靈活紊搪,可以更好的控制讀寫數(shù)據(jù)流的位置蜜葱。
簡(jiǎn)單的示例代碼:獲取倒數(shù)第二個(gè)字符(需要考慮 UTF-8 編碼,這里的代碼只是一個(gè)示例):
reader := strings.NewReader("Go語言中文網(wǎng)")
reader.Seek(-6, io.SeekEnd)
r, _, _ := reader.ReadRune()
fmt.Printf("%c\n", r)
小貼士
whence 的值耀石,在 io 包中定義了相應(yīng)的常量牵囤,應(yīng)該使用這些常量
const (
SeekStart = 0 // seek relative to the origin of the file
SeekCurrent = 1 // seek relative to the current offset
SeekEnd = 2 // seek relative to the end
)
而原先 os 包中的常量已經(jīng)被標(biāo)注為Deprecated
// Deprecated: Use io.SeekStart, io.SeekCurrent, and io.SeekEnd.
const (
SEEK_SET int = 0 // seek relative to the origin of the file
SEEK_CUR int = 1 // seek relative to the current offset
SEEK_END int = 2 // seek relative to the end
)
Closer接口
接口定義如下:
type Closer interface {
Close() error
}
該接口比較簡(jiǎn)單,只有一個(gè) Close() 方法滞伟,用于關(guān)閉數(shù)據(jù)流揭鳞。
文件 (os.File)、歸檔(壓縮包)梆奈、數(shù)據(jù)庫連接野崇、Socket 等需要手動(dòng)關(guān)閉的資源都實(shí)現(xiàn)了 Closer 接口。
實(shí)際編程中亩钟,經(jīng)常將 Close 方法的調(diào)用放在 defer 語句中乓梨。
小提示
初學(xué)者容易寫出這樣的代碼:
file, err := os.Open("studygolang.txt")
defer file.Close()
if err != nil {
...
}
?當(dāng)文件 studygolang.txt 不存在或找不到時(shí),file.Close() 會(huì) panic清酥,因?yàn)?file 是 nil扶镀。因此,應(yīng)該將 defer file.Close() 放在錯(cuò)誤檢查之后焰轻。
經(jīng)過 issue40 提醒臭觉,查看源碼:
func (f *File) Close() error {
if f == nil {
return ErrInvalid
}
return f.file.close()
}
可見并不會(huì) panic,但在 Close 之前校驗(yàn)錯(cuò)誤是個(gè)好習(xí)慣辱志!
其他接口
ByteReader 和 ByteWriter
通過名稱大概也能猜出這組接口的用途:讀或?qū)懸粋€(gè)字節(jié)蝠筑。接口定義如下:
type ByteReader interface {
ReadByte() (c byte, err error)
}
type ByteWriter interface {
WriteByte(c byte) error
}
在標(biāo)準(zhǔn)庫中,有如下類型實(shí)現(xiàn)了 io.ByteReader 或 io.ByteWriter:
-bufio.Reader/Writer 分別實(shí)現(xiàn)了io.ByteReader 和 io.ByteWriter
-bytes.Buffer 同時(shí)實(shí)現(xiàn)了 io.ByteReader 和 io.ByteWriter
-bytes.Reader 實(shí)現(xiàn)了 io.ByteReader
-strings.Reader 實(shí)現(xiàn)了 io.ByteReader
接下來的示例中揩懒,我們通過 bytes.Buffer 來一次讀取或?qū)懭胍粋€(gè)字節(jié)(主要代碼):
var ch byte
fmt.Scanf("%c\n", &ch)
buffer := new(bytes.Buffer)
err := buffer.WriteByte(ch)
if err == nil {
fmt.Println("寫入一個(gè)字節(jié)成功什乙!準(zhǔn)備讀取該字節(jié)……")
newCh, _ := buffer.ReadByte()
fmt.Printf("讀取的字節(jié):%c\n", newCh)
} else {
fmt.Println("寫入錯(cuò)誤")
}
?程序從標(biāo)準(zhǔn)輸入接收一個(gè)字節(jié)(ASCII 字符),調(diào)用 buffer 的 WriteByte 將該字節(jié)寫入 buffer 中旭从,之后通過 ReadByte 讀取該字節(jié)稳强。
?一般地,我們不會(huì)使用 bytes.Buffer 來一次讀取或?qū)懭胍粋€(gè)字節(jié)和悦。那么退疫,這兩個(gè)接口有哪些用處呢?
?在標(biāo)準(zhǔn)庫 encoding/binary 中鸽素,實(shí)現(xiàn)Google-ProtoBuf中的 Varints 讀取褒繁,ReadVarint 就需要一個(gè) io.ByteReader 類型的參數(shù),也就是說馍忽,它需要一個(gè)字節(jié)一個(gè)字節(jié)的讀取棒坏。關(guān)于 encoding/binary 包在后面會(huì)詳細(xì)介紹燕差。
?在標(biāo)準(zhǔn)庫 image/jpeg 中,Encode函數(shù)的內(nèi)部實(shí)現(xiàn)使用了 ByteWriter 寫入一個(gè)字節(jié)坝冕。
小貼士
?可以通過在 Go 語言源碼 src/pkg 中搜索 "io.ByteReader" 或 "io.ByteWiter"徒探,獲得哪些地方用到了這兩個(gè)接口。你會(huì)發(fā)現(xiàn)喂窟,這兩個(gè)接口在二進(jìn)制數(shù)據(jù)或歸檔壓縮時(shí)用的比較多测暗。
ByteScanner、RuneReader 和 RuneScanner
將這三個(gè)接口放在一起磨澡,是考慮到與 ByteReader 相關(guān)或相應(yīng)碗啄。
ByteScanner 接口的定義如下:
type ByteScanner interface {
ByteReader
UnreadByte() error
}
?可見,它內(nèi)嵌了 ByteReader 接口(可以理解為繼承了 ByteReader 接口)稳摄,UnreadByte 方法的意思是:將上一次 ReadByte 的字節(jié)還原稚字,使得再次調(diào)用 ReadByte 返回的結(jié)果和上一次調(diào)用相同,也就是說厦酬,UnreadByte 是重置上一次的 ReadByte胆描。注意,UnreadByte 調(diào)用之前必須調(diào)用了 ReadByte弃锐,且不能連續(xù)調(diào)用 UnreadByte袄友。即:
buffer := bytes.NewBuffer([]byte{'a', 'b'})
err := buffer.UnreadByte()
和
buffer := bytes.NewBuffer([]byte{'a', 'b'})
buffer.ReadByte()
err := buffer.UnreadByte()
err = buffer.UnreadByte()
?err 都 非nil,錯(cuò)誤為:bytes.Buffer: UnreadByte: previous operation was not a read
?RuneReader 接口和 ByteReader 類似霹菊,只是 ReadRune 方法讀取單個(gè) UTF-8 字符剧蚣,返回其 rune 和該字符占用的字節(jié)數(shù)。該接口在 regexp 包有用到旋廷。
問題:strings.Index("行業(yè)交流群", "交流") 返回的是單字節(jié)字符的位置:6鸠按。但是想要的是 unicode 字符的位置:2
這里借助utf8的RuneCountInString函數(shù),實(shí)現(xiàn)代碼如下:
// strings.Index 的 UTF-8 版本
// 即 Utf8Index("Go語言中文網(wǎng)", "中文") 返回 4,而不是 strings.Index 的 8
func Utf8Index(str, substr string) int {
index := strings.Index(str, substr)
if index < 0{
return -1
}
return utf8.RuneCountInString(str[:index])
}
RuneScanner 接口和 ByteScanner 類似饶碘,就不贅述了目尖。
ReadCloser、ReadSeeker扎运、ReadWriteCloser瑟曲、ReadWriteSeeker、ReadWriter豪治、WriteCloser 和 WriteSeeker 接口
這些接口是上面介紹的接口的兩個(gè)或三個(gè)組合而成的新接口洞拨。例如 ReadWriter 接口:
type ReadWriter interface {
Reader
Writer
}
?這是 Reader 接口和 Writer 接口的簡(jiǎn)單組合(內(nèi)嵌)。
?這些接口的作用是:有些時(shí)候同時(shí)需要某兩個(gè)接口的所有功能负拟,即必須同時(shí)實(shí)現(xiàn)了某兩個(gè)接口的類型才能夠被傳入使用烦衣。可見,io 包中有大量的“小接口”花吟,這樣方便組合為“大接口”秸歧。
SectionReader 類型
SectionReader 是一個(gè) struct(沒有任何導(dǎo)出的字段),實(shí)現(xiàn)了 Read, Seek 和 ReadAt衅澈,同時(shí)键菱,內(nèi)嵌了 ReaderAt 接口。結(jié)構(gòu)定義如下
type SectionReader struct {
r ReaderAt // 該類型最終的 Read/ReadAt 最終都是通過 r 的 ReadAt 實(shí)現(xiàn)
base int64 // NewSectionReader 會(huì)將 base 設(shè)置為 off
off int64 // 從 r 中的 off 偏移處開始讀取數(shù)據(jù)
limit int64 // limit - off = SectionReader 流的長(zhǎng)度
}
從名稱我們可以猜到矾麻,該類型讀取數(shù)據(jù)流中部分?jǐn)?shù)據(jù)纱耻。看一下
func NewSectionReader(r ReaderAt, off int64, n int64) *SectionReader
的文檔說明就知道了:
?NewSectionReader 返回一個(gè) SectionReader险耀,它從 r 中的偏移量 off 處讀取 n 個(gè)字節(jié)后以 EOF 停止。
?也就是說玖喘,SectionReader 只是內(nèi)部(內(nèi)嵌)ReaderAt 表示的數(shù)據(jù)流的一部分:從 off 開始后的 n 個(gè)字節(jié)甩牺。
?這個(gè)類型的作用是:方便重復(fù)操作某一段 (section) 數(shù)據(jù)流;或者同時(shí)需要 ReadAt 和 Seek 的功能累奈。
?由于該類型所支持的操作贬派,前面都有介紹,因此不提供示例代碼了澎媒。
LimitedReader 類型
LimitedReader 結(jié)構(gòu)定義如下:
type LimitedReader struct {
R Reader // underlying reader搞乏,最終的讀取操作通過 R.Read 完成
N int64 // max bytes remaining
}
文檔說明如下:
從 R 讀取但將返回的數(shù)據(jù)量限制為 N 字節(jié)。每調(diào)用一次 Read 都將更新 N 來反應(yīng)新的剩余數(shù)量戒努。
也就是說请敦,最多只能返回 N 字節(jié)數(shù)據(jù)。
LimitedReader 只實(shí)現(xiàn)了 Read 方法(Reader 接口)储玫。
使用示例如下:
content := "This Is LimitReader Example"
reader := strings.NewReader(content)
limitReader := &io.LimitedReader{R: reader, N: 8}
for limitReader.N > 0 {
tmp := make([]byte, 2)
limitReader.Read(tmp)
fmt.Printf("%s", tmp)
}
//輸出:This Is
可見侍筛,通過該類型可以達(dá)到 只允許讀取一定長(zhǎng)度數(shù)據(jù) 的目的。
在 io 包中撒穷,LimitReader 函數(shù)的實(shí)現(xiàn)其實(shí)就是調(diào)用 LimitedReader:
func LimitReader(r Reader, n int64) Reader { return &LimitedReader{r, n} }
PipeReader 和 PipeWriter 類型
PipeReader(一個(gè)沒有任何導(dǎo)出字段的 struct)是管道的讀取端匣椰。它實(shí)現(xiàn)了 io.Reader 和 io.Closer 接口。結(jié)構(gòu)定義如下:
type PipeReader struct {
p *pipe
}
?關(guān)于 PipeReader.Read 方法的說明:從管道中讀取數(shù)據(jù)端礼。該方法會(huì)堵塞禽笑,直到管道寫入端開始寫入數(shù)據(jù)或?qū)懭攵吮魂P(guān)閉。如果寫入端關(guān)閉時(shí)帶有 error(即調(diào)用 CloseWithError 關(guān)閉)蛤奥,該Read返回的 err 就是寫入端傳遞的error佳镜;否則 err 為 EOF。
?PipeWriter(一個(gè)沒有任何導(dǎo)出字段的 struct)是管道的寫入端喻括。它實(shí)現(xiàn)了 io.Writer 和 io.Closer 接口邀杏。結(jié)構(gòu)定義如下:
type PipeWriter struct {
p *pipe
}
?關(guān)于 PipeWriter.Write 方法的說明:寫數(shù)據(jù)到管道中。該方法會(huì)堵塞,直到管道讀取端讀完所有數(shù)據(jù)或讀取端被關(guān)閉望蜡。如果讀取端關(guān)閉時(shí)帶有 error(即調(diào)用 CloseWithError 關(guān)閉)唤崭,該Write返回的 err 就是讀取端傳遞的error;否則 err 為 ErrClosedPipe脖律。
使用示例如下:
func main() {
pipeReader, pipeWriter := io.Pipe()
go PipeWrite(pipeWriter)
go PipeRead(pipeReader)
time.Sleep(30 * time.Second)
}
func PipeWrite(writer *io.PipeWriter){
data := []byte("Go語言中文網(wǎng)")
for i := 0; i < 3; i++{
n, err := writer.Write(data)
if err != nil{
fmt.Println(err)
return
}
fmt.Printf("寫入字節(jié) %d\n",n)
}
writer.CloseWithError(errors.New("寫入段已關(guān)閉"))
}
func PipeRead(reader *io.PipeReader){
buf := make([]byte, 128)
for{
fmt.Println("接口端開始阻塞5秒鐘...")
time.Sleep(5 * time.Second)
fmt.Println("接收端開始接受")
n, err := reader.Read(buf)
if err != nil{
fmt.Println(err)
return
}
fmt.Printf("收到字節(jié): %d\n buf內(nèi)容: %s\n",n,buf)
}
}
io.Pipe() 用于創(chuàng)建一個(gè)同步的內(nèi)存管道 (synchronous in-memory pipe)谢肾,函數(shù)簽名:
func Pipe() (*PipeReader, *PipeWriter)
?它將 io.Reader 連接到 io.Writer。一端的讀取匹配另一端的寫入小泉,直接在這兩端之間復(fù)制數(shù)據(jù)芦疏;它沒有內(nèi)部緩存。它對(duì)于并行調(diào)用 Read 和 Write 以及其它函數(shù)或 Close 來說都是安全的微姊。一旦等待的 I/O 結(jié)束酸茴,Close 就會(huì)完成。并行調(diào)用 Read 或并行調(diào)用 Write 也同樣安全:同種類的調(diào)用將按順序進(jìn)行控制兢交。
?正因?yàn)槭峭降男胶矗虼瞬荒茉谝粋€(gè) goroutine 中進(jìn)行讀和寫。
?另外配喳,對(duì)于管道的 close 方法(非 CloseWithError 時(shí))酪穿,err 會(huì)被置為 EOF。
Copy 和 CopyN 函數(shù)
Copy 函數(shù)的簽名:
func Copy(dst Writer, src Reader) (written int64, err error)
函數(shù)文檔:
?Copy 將 src 復(fù)制到 dst晴裹,直到在 src 上到達(dá) EOF 或發(fā)生錯(cuò)誤被济。它返回復(fù)制的字節(jié)數(shù),如果有錯(cuò)誤的話涧团,還會(huì)返回在復(fù)制時(shí)遇到的第一個(gè)錯(cuò)誤只磷。
?成功的 Copy 返回 err == nil,而非 err == EOF少欺。由于 Copy 被定義為從 src 讀取直到 EOF 為止喳瓣,因此它不會(huì)將來自 Read 的 EOF 當(dāng)做錯(cuò)誤來報(bào)告。
?若 dst 實(shí)現(xiàn)了 ReaderFrom 接口赞别,其復(fù)制操作可通過調(diào)用 dst.ReadFrom(src) 實(shí)現(xiàn)畏陕。此外,若 src 實(shí)現(xiàn)了 WriterTo 接口仿滔,其復(fù)制操作可通過調(diào)用 src.WriteTo(dst) 實(shí)現(xiàn)惠毁。
代碼:
io.Copy(os.Stdout, strings.NewReader("Go語言中文網(wǎng)"))
直接將內(nèi)容輸出(寫入 Stdout 中)。
我們甚至可以這么做:
package main
import (
"fmt"
"io"
"os"
)
func main() {
io.Copy(os.Stdout, os.Stdin)
fmt.Println("Got EOF -- bye")
}
執(zhí)行:echo "Hello, World" | go run main.go
CopyN 函數(shù)的簽名:
func CopyN(dst Writer, src Reader, n int64) (written int64, err error)
函數(shù)文檔:
CopyN 將 n 個(gè)字節(jié)(或到一個(gè)error)從 src 復(fù)制到 dst崎页。 它返回復(fù)制的字節(jié)數(shù)以及在復(fù)制時(shí)遇到的最早的錯(cuò)誤鞠绰。當(dāng)且僅當(dāng)err == nil時(shí),written == n 。
若 dst 實(shí)現(xiàn)了 ReaderFrom 接口飒焦,復(fù)制操作也就會(huì)使用它來實(shí)現(xiàn)蜈膨。
代碼:
io.CopyN(os.Stdout, strings.NewReader("Go語言中文網(wǎng)"), 8)
//會(huì)輸出:Go語言
ReadAtLeast 和 ReadFull 函數(shù)
ReadAtLeast 函數(shù)的簽名:
func ReadAtLeast(r Reader, buf []byte, min int) (n int, err error)
函數(shù)文檔:
ReadAtLeast 將 r 讀取到 buf 中屿笼,直到讀了最少 min 個(gè)字節(jié)為止。它返回復(fù)制的字節(jié)數(shù)翁巍,如果讀取的字節(jié)較少驴一,還會(huì)返回一個(gè)錯(cuò)誤。若沒有讀取到字節(jié)灶壶,錯(cuò)誤就只是 EOF肝断。如果一個(gè) EOF 發(fā)生在讀取了少于 min 個(gè)字節(jié)之后,ReadAtLeast 就會(huì)返回 ErrUnexpectedEOF驰凛。若 min 大于 buf 的長(zhǎng)度胸懈,ReadAtLeast 就會(huì)返回 ErrShortBuffer。對(duì)于返回值恰响,當(dāng)且僅當(dāng) err == nil 時(shí)趣钱,才有 n >= min。
一般可能不太會(huì)用到這個(gè)函數(shù)渔隶。使用時(shí)需要注意返回的 error 判斷羔挡。
ReadFull 函數(shù)的簽名:
func ReadFull(r Reader, buf []byte) (n int, err error)
函數(shù)文檔:
ReadFull 精確地從 r 中將 len(buf) 個(gè)字節(jié)讀取到 buf 中。它返回復(fù)制的字節(jié)數(shù)间唉,如果讀取的字節(jié)較少,還會(huì)返回一個(gè)錯(cuò)誤利术。若沒有讀取到字節(jié)呈野,錯(cuò)誤就只是 EOF。如果一個(gè) EOF 發(fā)生在讀取了一些但不是所有的字節(jié)后印叁,ReadFull 就會(huì)返回 ErrUnexpectedEOF被冒。對(duì)于返回值,當(dāng)且僅當(dāng) err == nil 時(shí)轮蜕,才有 n == len(buf)昨悼。
注意該函數(shù)和 ReadAtLeast 的區(qū)別:ReadFull 將 buf 讀滿;而 ReadAtLeast 是最少讀取 min 個(gè)字節(jié)跃洛。
WriteString 函數(shù)
這是為了方便寫入 string 類型提供的函數(shù)率触,函數(shù)簽名:
func WriteString(w Writer, s string) (n int, err error)
函數(shù)文檔:
WriteString 將s的內(nèi)容寫入w中,當(dāng) w 實(shí)現(xiàn)了 WriteString 方法時(shí)汇竭,會(huì)直接調(diào)用該方法葱蝗,否則執(zhí)行 w.Write([]byte(s))。
MultiReader 和 MultiWriter 函數(shù)
這兩個(gè)函數(shù)的定義分別是:
func MultiReader(readers ...Reader) Reader
func MultiWriter(writers ...Writer) Writer
它們接收多個(gè) Reader 或 Writer细燎,返回一個(gè) Reader 或 Writer两曼。我們可以猜想到這兩個(gè)函數(shù)就是操作多個(gè) Reader 或 Writer 就像操作一個(gè)。
事實(shí)上玻驻,在 io 包中定義了兩個(gè)非導(dǎo)出類型:mutilReader 和 multiWriter悼凑,它們分別實(shí)現(xiàn)了 io.Reader 和 io.Writer 接口。類型定義為:
type multiReader struct {
readers []Reader
}
type multiWriter struct {
writers []Writer
}
對(duì)于這兩種類型對(duì)應(yīng)的實(shí)現(xiàn)方法(Read 和 Write 方法)的使用,我們通過例子來演示户辫。
MultiReader 的使用:
readers := []io.Reader{
strings.NewReader("from strings reader"),
bytes.NewBufferString("from bytes buffer"),
}
reader := io.MultiReader(readers...)
data := make([]byte, 0, 128)
buf := make([]byte, 10)
for n, err := reader.Read(buf); err != io.EOF ; n, err = reader.Read(buf){
if err != nil{
panic(err)
}
data = append(data,buf[:n]...)
}
fmt.Printf("%s\n", data)
//輸出:from strings readerfrom bytes buffer
代碼中首先構(gòu)造了一個(gè) io.Reader 的 slice渐夸,由 strings.Reader 和 bytes.Buffer 兩個(gè)實(shí)例組成,然后通過 MultiReader 得到新的 Reader寸莫,循環(huán)讀取新 Reader 中的內(nèi)容捺萌。從輸出結(jié)果可以看到,第一次調(diào)用 Reader 的 Read 方法獲取到的是 slice 中第一個(gè)元素的內(nèi)容……也就是說膘茎,MultiReader 只是邏輯上將多個(gè) Reader 組合起來桃纯,并不能通過調(diào)用一次 Read 方法獲取所有 Reader 的內(nèi)容。在所有的 Reader 內(nèi)容都被讀完后披坏,Reader 會(huì)返回 EOF态坦。
MultiWriter 的使用:
file, err := os.Create("tmp.txt")
if err != nil {
panic(err)
}
defer file.Close()
writers := []io.Writer{
file,
os.Stdout,
}
writer := io.MultiWriter(writers...)
writer.Write([]byte("Go語言中文網(wǎng)"))
這段程序執(zhí)行后在生成 tmp.txt 文件,同時(shí)在文件和屏幕中都輸出:Go語言中文網(wǎng)棒拂。這和 Unix 中的 tee 命令類似伞梯。
動(dòng)手試試
Go 實(shí)現(xiàn) Unix 中 tee 命令的功能很簡(jiǎn)單吧。MultiWriter 的 Write 方法是如何實(shí)現(xiàn)的帚屉?有興趣可以自己實(shí)現(xiàn)一個(gè)谜诫,然后對(duì)著源碼比較一下。
TeeReader函數(shù)
函數(shù)簽名如下:
func TeeReader(r Reader, w Writer) Reader
TeeReader 返回一個(gè) Reader攻旦,它將從 r 中讀到的數(shù)據(jù)寫入 w 中漾唉。所有經(jīng)由它處理的從 r 的讀取都匹配于對(duì)應(yīng)的對(duì) w 的寫入谦铃。它沒有內(nèi)部緩存婶博,即寫入必須在讀取完成前完成耙册。任何在寫入時(shí)遇到的錯(cuò)誤都將作為讀取錯(cuò)誤返回。
也就是說烙无,我們通過 Reader 讀取內(nèi)容后锋谐,會(huì)自動(dòng)寫入到 Writer 中去。例子代碼如下:
reader := io.TeeReader(strings.NewReader("Go語言中文網(wǎng)"), os.Stdout)
reader.Read(make([]byte, 20))
//輸出結(jié)果:Go語言中文網(wǎng)
這種功能的實(shí)現(xiàn)其實(shí)挺簡(jiǎn)單截酷,無非是在 Read 完后執(zhí)行 Write涮拗。
至此,io 所有接口合搅、類型和函數(shù)都講解完成多搀。