原文鏈接:基本的 IO 接口
原文鏈接:方便的IO操作函數集
圖片來源:圖片顯示來源
1. 1.1 io — 基本的 IO 接口
io 包為 I/O 原語提供了基本的接口。它主要包裝了這些原語的已有實現。
由於這些被接口包裝的I/O原語是由不同的低級操作實現,因此,在另有聲明之前不該假定它們的並行執行是安全的。
在 io 包中最重要的是兩個接口:Reader 和 Writer 接口。本章所提到的各種 IO 包,都跟這兩個接口有關,也就是說,只要滿足這兩個接口,它就可以使用 IO 包的功能。
1.1. 概覽
1.2. 邏輯
1.3. Reader 接口
Reader 接口的定義如下:
type Reader interface { Read(p []byte) (n int, err error) }
官方文檔中關於該接口方法的說明:
Read 將 len(p) 個字節讀取到 p 中。它返回讀取的字節數 n(0 <= n <= len(p)) 以及任何遇到的錯誤。即使 Read 返回的 n < len(p),它也會在調用過程中占用 len(p) 個字節作為暫存空間。若可讀取的數據不到 len(p) 個字節,Read 會返回可用數據,而不是等待更多數據。
當 Read 在成功讀取 n > 0 個字節后遇到一個錯誤或 EOF (end-of-file),它會返回讀取的字節數。它可能會同時在本次的調用中返回一個non-nil錯誤,或在下一次的調用中返回這個錯誤(且 n 為 0)。 一般情況下, Reader會返回一個非0字節數n, 若 n = len(p) 個字節從輸入源的結尾處由 Read 返回,Read可能返回 err == EOF 或者 err == nil。並且之后的 Read() 都應該返回 (n:0, err:EOF)。
調用者在考慮錯誤之前應當首先處理返回的數據。這樣做可以正確地處理在讀取一些字節后產生的 I/O 錯誤,同時允許EOF的出現。
根據 Go 語言中關於接口和滿足了接口的類型的定義(Interface_types),我們知道 Reader 接口的方法集(Method_sets)只包含一個 Read 方法,因此,所有實現了 Read 方法的類型都滿足 io.Reader 接口,也就是說,在所有需要 io.Reader 的地方,可以傳遞實現了 Read() 方法的類型的實例。
下面,我們通過具體例子來談談該接口的用法。
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 }
ReadFrom 函數將 io.Reader 作為參數,也就是說,ReadFrom 可以從任意的地方讀取數據,只要來源實現了 io.Reader 接口。比如,我們可以從標准輸入、文件、字符串等讀取數據,示例代碼如下:
// 從標准輸入讀取 data, err = ReadFrom(os.Stdin, 11) // 從普通文件讀取,其中 file 是 os.File 的實例 data, err = ReadFrom(file, 9) // 從字符串讀取 data, err = ReadFrom(strings.NewReader("from string"), 12)
完整的演示例子源碼見 code/src/chapter01/io/reader.go
另外的例子:(使用go中已經實現該接口的bytes類型)
reader := bytes.NewReader([]byte("hello world")) p := make([]byte,11) reader.Read(p) fmt.Printf("%x\n",p) //68656c6c6f20776f726c64 fmt.Printf("%s\n",p) //hello world
小貼士
io.EOF 變量的定義:var EOF = errors.New("EOF")
,是 error 類型。根據 reader 接口的說明,在 n > 0 且數據被讀完了的情況下,當次返回的 error 有可能是 EOF 也有可能是 nil。
1.4. Writer 接口
Writer 接口的定義如下:
type Writer interface { Write(p []byte) (n int, err error) }
官方文檔中關於該接口方法的說明:
Write 將 len(p) 個字節從 p 中寫入到基本數據流中。它返回從 p 中被寫入的字節數 n(0 <= n <= len(p))以及任何遇到的引起寫入提前停止的錯誤。若 Write 返回的 n < len(p),它就必須返回一個 非nil 的錯誤。
同樣的,所有實現了Write方法的類型都實現了 io.Writer 接口。
在上個例子中,我們是自己實現一個函數接收一個 io.Reader 類型的參數。這里,我們通過標准庫的例子來學習。
在fmt標准庫中,有一組函數:Fprint/Fprintf/Fprintln,它們接收一個 io.Wrtier 類型參數(第一個參數),也就是說它們將數據格式化輸出到 io.Writer 中。那么,調用這組函數時,該如何傳遞這個參數呢?
我們以 fmt.Fprintln 為例,同時看一下 fmt.Println 函數的源碼。
func Println(a ...interface{}) (n int, err error) { return Fprintln(os.Stdout, a...) }
很顯然,fmt.Println會將內容輸出到標准輸出中。下一節我們將詳細介紹fmt包。
另外的例子:
s := []byte("hello world") buf := new(bytes.Buffer) buf.Write(s) fmt.Printf("% x\n", buf.Bytes()) //68 65 6c 6c 6f 20 77 6f 72 6c 64 fmt.Printf("% s\n", buf.Bytes()) //hello world
關於 io.Writer 的更多說明,可以查看筆者之前寫的博文《以io.Writer為例看go中的interface{}》。
1.5. 實現了 io.Reader 接口或 io.Writer 接口的類型
初學者看到函數參數是一個接口類型,很多時候有些束手無策,不知道該怎么傳遞參數。還有人問:標准庫中有哪些類型實現了 io.Reader 或 io.Writer 接口?
通過本節上面的例子,我們可以知道,os.File 同時實現了這兩個接口。我們還看到 os.Stdin/Stdout 這樣的代碼,它們似乎分別實現了 io.Reader/io.Writer 接口。沒錯,實際上在 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 只是三個特殊的文件類型的標識(即都是 os.File 的實例),自然也實現了 io.Reader 和 io.Writer。
目前,Go 文檔中還沒有直接列出實現了某個接口的所有類型。不過,我們可以通過查看標准庫文檔,列出實現了 io.Reader 或 io.Writer 接口的類型(導出的類型):(注:godoc 命令支持額外參數 -analysis ,能列出都有哪些類型實現了某個接口,相關參考 godoc -h
或 Static analysis features of godoc。另外,我做了一個官網鏡像,能查看接口所有的實現類型,地址:http://docs.studygolang.com。
- os.File 同時實現了 io.Reader 和 io.Writer
- strings.Reader 實現了 io.Reader
- bufio.Reader/Writer 分別實現了 io.Reader 和 io.Writer
- bytes.Buffer 同時實現了 io.Reader 和 io.Writer
- bytes.Reader 實現了 io.Reader
- compress/gzip.Reader/Writer 分別實現了 io.Reader 和 io.Writer
- crypto/cipher.StreamReader/StreamWriter 分別實現了 io.Reader 和 io.Writer
- crypto/tls.Conn 同時實現了 io.Reader 和 io.Writer
- encoding/csv.Reader/Writer 分別實現了 io.Reader 和 io.Writer
- mime/multipart.Part 實現了 io.Reader
- net/conn 分別實現了 io.Reader 和 io.Writer(Conn接口定義了Read/Write)
除此之外,io 包本身也有這兩個接口的實現類型。如:
實現了 Reader 的類型:LimitedReader、PipeReader、SectionReader
實現了 Writer 的類型:PipeWriter
以上類型中,常用的類型有:os.File、strings.Reader、bufio.Reader/Writer、bytes.Buffer、bytes.Reader
小貼士
從接口名稱很容易猜到,一般地, Go 中接口的命名約定:接口名以 er 結尾。注意,這里並非強行要求,你完全可以不以 er 結尾。標准庫中有些接口也不是以 er 結尾的。
1.6. ReaderAt 和 WriterAt 接口
ReaderAt 接口的定義如下:
type ReaderAt interface { ReadAt(p []byte, off int64) (n int, err error) }
官方文檔中關於該接口方法的說明:
ReadAt 從基本輸入源的偏移量 off 處開始,將 len(p) 個字節讀取到 p 中。它返回讀取的字節數 n(0 <= n <= len(p))以及任何遇到的錯誤。
當 ReadAt 返回的 n < len(p) 時,它就會返回一個 非nil 的錯誤來解釋 為什么沒有返回更多的字節。在這一點上,ReadAt 比 Read 更嚴格。
即使 ReadAt 返回的 n < len(p),它也會在調用過程中使用 p 的全部作為暫存空間。若可讀取的數據不到 len(p) 字節,ReadAt 就會阻塞,直到所有數據都可用或一個錯誤發生。 在這一點上 ReadAt 不同於 Read。
若 n = len(p) 個字節從輸入源的結尾處由 ReadAt 返回,Read可能返回 err == EOF 或者 err == nil
若 ReadAt 攜帶一個偏移量從輸入源讀取,ReadAt 應當既不影響偏移量也不被它所影響。
可對相同的輸入源並行執行 ReadAt 調用。
可見,ReaderAt 接口使得可以從指定偏移量處開始讀取數據。
簡單示例代碼如下:
reader := strings.NewReader("Go語言中文網") p := make([]byte, 6) n, err := reader.ReadAt(p, 2) if err != nil { panic(err) } fmt.Printf("%s, %d\n", p, n)
輸出:
語言, 6
WriterAt 接口的定義如下:
type WriterAt interface { WriteAt(p []byte, off int64) (n int, err error) }
官方文檔中關於該接口方法的說明:
WriteAt 從 p 中將 len(p) 個字節寫入到偏移量 off 處的基本數據流中。它返回從 p 中被寫入的字節數 n(0 <= n <= len(p))以及任何遇到的引起寫入提前停止的錯誤。若 WriteAt 返回的 n < len(p),它就必須返回一個 非nil 的錯誤。
若 WriteAt 攜帶一個偏移量寫入到目標中,WriteAt 應當既不影響偏移量也不被它所影響。
若被寫區域沒有重疊,可對相同的目標並行執行 WriteAt 調用。
我們可以通過該接口將數據寫入到數據流的特定偏移量之后。
通過簡單示例來演示 WriteAt 方法的使用(os.File 實現了 WriterAt 接口):
file, err := os.Create("writeAt.txt") if err != nil { panic(err) } defer file.Close() file.WriteString("Golang中文社區——這里是多余") n, err := file.WriteAt([]byte("Go語言中文網"), 24) if err != nil { panic(err) } fmt.Println(n)
打開文件 WriteAt.txt,內容是:Golang中文社區——Go語言中文網
。
分析:
file.WriteString("Golang中文社區——這里是多余")
往文件中寫入 Golang中文社區——這里是多余
,之后 file.WriteAt([]byte("Go語言中文網"), 24)
在文件流的 offset=24 處寫入 Go語言中文網
(會覆蓋該位置的內容)。
1.7. ReaderFrom 和 WriterTo 接口
ReaderFrom 的定義如下:
type ReaderFrom interface { ReadFrom(r Reader) (n int64, err error) }
官方文檔中關於該接口方法的說明:
ReadFrom 從 r 中讀取數據,直到 EOF 或發生錯誤。其返回值 n 為讀取的字節數。除 io.EOF 之外,在讀取過程中遇到的任何錯誤也將被返回。
如果 ReaderFrom 可用,Copy 函數就會使用它。
注意:ReadFrom 方法不會返回 err == EOF。
下面的例子簡單的實現將文件中的數據全部讀取(顯示在標准輸出):
file, err := os.Open("writeAt.txt") if err != nil { panic(err) } defer file.Close() writer := bufio.NewWriter(os.Stdout) writer.ReadFrom(file) writer.Flush()
當然,我們可以通過 ioutil 包的 ReadFile 函數獲取文件全部內容。其實,跟蹤一下 ioutil.ReadFile 的源碼,會發現其實也是通過 ReadFrom 方法實現(用的是 bytes.Buffer,它實現了 ReaderFrom 接口)。
如果不通過 ReadFrom 接口來做這件事,而是使用 io.Reader 接口,我們有兩種思路:
- 先獲取文件的大小(File 的 Stat 方法),之后定義一個該大小的 []byte,通過 Read 一次性讀取
- 定義一個小的 []byte,不斷的調用 Read 方法直到遇到 EOF,將所有讀取到的 []byte 連接到一起
這里不給出實現代碼了,有興趣的可以實現一下。
提示
通過查看 bufio.Writer 或 strings.Buffer 類型的 ReadFrom 方法實現,會發現,其實它們的實現和上面說的第 2 種思路類似。
WriterTo的定義如下:
type WriterTo interface { WriteTo(w Writer) (n int64, err error) }
官方文檔中關於該接口方法的說明:
WriteTo 將數據寫入 w 中,直到沒有數據可寫或發生錯誤。其返回值 n 為寫入的字節數。 在寫入過程中遇到的任何錯誤也將被返回。
如果 WriterTo 可用,Copy 函數就會使用它。
讀者是否發現,其實 ReaderFrom 和 WriterTo 接口的方法接收的參數是 io.Reader 和 io.Writer 類型。根據 io.Reader 和 io.Writer 接口的講解,對該接口的使用應該可以很好的掌握。
這里只提供簡單的一個示例代碼:將一段文本輸出到標准輸出
reader := bytes.NewReader([]byte("Go語言中文網")) reader.WriteTo(os.Stdout)
通過 io.ReaderFrom 和 io.WriterTo 的學習,我們知道,如果這樣的需求,可以考慮使用這兩個接口:“一次性從某個地方讀或寫到某個地方去。”
1.8. Seeker 接口
接口定義如下:
type Seeker interface { Seek(offset int64, whence int) (ret int64, err error) }
官方文檔中關於該接口方法的說明:
Seek 設置下一次 Read 或 Write 的偏移量為 offset,它的解釋取決於 whence: 0 表示相對於文件的起始處,1 表示相對於當前的偏移,而 2 表示相對於其結尾處。 Seek 返回新的偏移量和一個錯誤,如果有的話。
也就是說,Seek 方法是用於設置偏移量的,這樣可以從某個特定位置開始操作數據流。聽起來和 ReaderAt/WriteAt 接口有些類似,不過 Seeker 接口更靈活,可以更好的控制讀寫數據流的位置。
簡單的示例代碼:獲取倒數第二個字符(需要考慮 UTF-8 編碼,這里的代碼只是一個示例)
reader := strings.NewReader("Go語言中文網") reader.Seek(-6, io.SeekEnd) r, _, _ := reader.ReadRune() fmt.Printf("%c\n", r)
小貼士
whence 的值,在 io 包中定義了相應的常量,應該使用這些常量
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 包中的常量已經被標注為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 )
1.9. Closer接口
接口定義如下:
type Closer interface { Close() error }
該接口比較簡單,只有一個 Close() 方法,用於關閉數據流。
文件 (os.File)、歸檔(壓縮包)、數據庫連接、Socket 等需要手動關閉的資源都實現了 Closer 接口。
實際編程中,經常將 Close 方法的調用放在 defer 語句中。
小提示
初學者容易寫出這樣的代碼:
file, err := os.Open("studygolang.txt") defer file.Close() if err != nil { ... }
當文件 studygolang.txt 不存在或找不到時,file.Close() 會 panic,因為 file 是 nil。因此,應該將 defer file.Close() 放在錯誤檢查之后。
經過 issue40 提醒,查看源碼:
func (f *File) Close() error { if f == nil { return ErrInvalid } return f.file.close() }
可見並不會 panic,但在 Close 之前校驗錯誤是個好習慣!
1.10. 其他接口
1.10.1. ByteReader 和 ByteWriter
通過名稱大概也能猜出這組接口的用途:讀或寫一個字節。接口定義如下:
type ByteReader interface { ReadByte() (c byte, err error) } type ByteWriter interface { WriteByte(c byte) error }
在標准庫中,有如下類型實現了 io.ByteReader 或 io.ByteWriter:
- bufio.Reader/Writer 分別實現了io.ByteReader 和 io.ByteWriter
- bytes.Buffer 同時實現了 io.ByteReader 和 io.ByteWriter
- bytes.Reader 實現了 io.ByteReader
- strings.Reader 實現了 io.ByteReader
接下來的示例中,我們通過 bytes.Buffer 來一次讀取或寫入一個字節(主要代碼):
var ch byte fmt.Scanf("%c\n", &ch) buffer := new(bytes.Buffer) err := buffer.WriteByte(ch) if err == nil { fmt.Println("寫入一個字節成功!准備讀取該字節……") newCh, _ := buffer.ReadByte() fmt.Printf("讀取的字節:%c\n", newCh) } else { fmt.Println("寫入錯誤") }
程序從標准輸入接收一個字節(ASCII 字符),調用 buffer 的 WriteByte 將該字節寫入 buffer 中,之后通過 ReadByte 讀取該字節。完整的代碼見:code/src/chapter01/io/byterwer.go
一般地,我們不會使用 bytes.Buffer 來一次讀取或寫入一個字節。那么,這兩個接口有哪些用處呢?
在標准庫 encoding/binary 中,實現Google-ProtoBuf中的 Varints 讀取,ReadVarint 就需要一個 io.ByteReader 類型的參數,也就是說,它需要一個字節一個字節的讀取。關於 encoding/binary 包在后面會詳細介紹。
在標准庫 image/jpeg 中,Encode函數的內部實現使用了 ByteWriter 寫入一個字節。
小貼士
可以通過在 Go 語言源碼 src/pkg 中搜索 "io.ByteReader" 或 "io.ByteWiter",獲得哪些地方用到了這兩個接口。你會發現,這兩個接口在二進制數據或歸檔壓縮時用的比較多。
1.10.2. ByteScanner、RuneReader 和 RuneScanner
將這三個接口放在一起,是考慮到與 ByteReader 相關或相應。
ByteScanner 接口的定義如下:
type ByteScanner interface { ByteReader UnreadByte() error }
可見,它內嵌了 ByteReader 接口(可以理解為繼承了 ByteReader 接口),UnreadByte 方法的意思是:將上一次 ReadByte 的字節還原,使得再次調用 ReadByte 返回的結果和上一次調用相同,也就是說,UnreadByte 是重置上一次的 ReadByte。注意,UnreadByte 調用之前必須調用了 ReadByte,且不能連續調用 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,錯誤為:bytes.Buffer: UnreadByte: previous operation was not a read
RuneReader 接口和 ByteReader 類似,只是 ReadRune 方法讀取單個 UTF-8 字符,返回其 rune 和該字符占用的字節數。該接口在 regexp 包有用到。
之前有人在QQ群中問道:
strings.Index("行業交流群", "交流") 返回的是單字節字符的位置:6。但是想要的是 unicode 字符的位置:2。
這里借助utf8的RuneCountInString函數,實現代碼如下:
// strings.Index 的 UTF-8 版本 // 即 Utf8Index("Go語言中文網", "中文") 返回 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 類似,就不贅述了。
1.10.3. ReadCloser、ReadSeeker、ReadWriteCloser、ReadWriteSeeker、ReadWriter、WriteCloser 和 WriteSeeker 接口
這些接口是上面介紹的接口的兩個或三個組合而成的新接口。例如 ReadWriter 接口:
type ReadWriter interface { Reader Writer }
這是 Reader 接口和 Writer 接口的簡單組合(內嵌)。
這些接口的作用是:有些時候同時需要某兩個接口的所有功能,即必須同時實現了某兩個接口的類型才能夠被傳入使用。可見,io 包中有大量的“小接口”,這樣方便組合為“大接口”。
1.11. SectionReader 類型
SectionReader 是一個 struct(沒有任何導出的字段),實現了 Read, Seek 和 ReadAt,同時,內嵌了 ReaderAt 接口。結構定義如下:
type SectionReader struct { r ReaderAt // 該類型最終的 Read/ReadAt 最終都是通過 r 的 ReadAt 實現 base int64 // NewSectionReader 會將 base 設置為 off off int64 // 從 r 中的 off 偏移處開始讀取數據 limit int64 // limit - off = SectionReader 流的長度 }
從名稱我們可以猜到,該類型讀取數據流中部分數據。看一下
func NewSectionReader(r ReaderAt, off int64, n int64) *SectionReader
的文檔說明就知道了:
NewSectionReader 返回一個 SectionReader,它從 r 中的偏移量 off 處讀取 n 個字節后以 EOF 停止。
也就是說,SectionReader 只是內部(內嵌)ReaderAt 表示的數據流的一部分:從 off 開始后的 n 個字節。
這個類型的作用是:方便重復操作某一段 (section) 數據流;或者同時需要 ReadAt 和 Seek 的功能。
由於該類型所支持的操作,前面都有介紹,因此不提供示例代碼了。
關於該類型在標准庫中的使用,我們在 8.5 archive/zip — zip歸檔訪問 會講到。
1.12. LimitedReader 類型
LimitedReader 結構定義如下:
type LimitedReader struct { R Reader // underlying reader,最終的讀取操作通過 R.Read 完成 N int64 // max bytes remaining }
文檔說明如下:
從 R 讀取但將返回的數據量限制為 N 字節。每調用一次 Read 都將更新 N 來反應新的剩余數量。
也就是說,最多只能返回 N 字節數據。
LimitedReader 只實現了 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
可見,通過該類型可以達到 只允許讀取一定長度數據 的目的。
在 io 包中,LimitReader 函數的實現其實就是調用 LimitedReader:
func LimitReader(r Reader, n int64) Reader { return &LimitedReader{r, n} }
1.13. PipeReader 和 PipeWriter 類型
PipeReader(一個沒有任何導出字段的 struct)是管道的讀取端。它實現了 io.Reader 和 io.Closer 接口。結構定義如下:
type PipeReader struct { p *pipe }
關於 PipeReader.Read 方法的說明:從管道中讀取數據。該方法會堵塞,直到管道寫入端開始寫入數據或寫入端被關閉。如果寫入端關閉時帶有 error(即調用 CloseWithError 關閉),該Read返回的 err 就是寫入端傳遞的error;否則 err 為 EOF。
PipeWriter(一個沒有任何導出字段的 struct)是管道的寫入端。它實現了 io.Writer 和 io.Closer 接口。結構定義如下:
type PipeWriter struct { p *pipe }
關於 PipeWriter.Write 方法的說明:寫數據到管道中。該方法會堵塞,直到管道讀取端讀完所有數據或讀取端被關閉。如果讀取端關閉時帶有 error(即調用 CloseWithError 關閉),該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語言中文網") for i := 0; i < 3; i++{ n, err := writer.Write(data) if err != nil{ fmt.Println(err) return } fmt.Printf("寫入字節 %d\n",n) } writer.CloseWithError(errors.New("寫入段已關閉")) } 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("收到字節: %d\n buf內容: %s\n",n,buf) } }
io.Pipe() 用於創建一個同步的內存管道 (synchronous in-memory pipe),函數簽名:
func Pipe() (*PipeReader, *PipeWriter)
它將 io.Reader 連接到 io.Writer。一端的讀取匹配另一端的寫入,直接在這兩端之間復制數據;它沒有內部緩存。它對於並行調用 Read 和 Write 以及其它函數或 Close 來說都是安全的。一旦等待的 I/O 結束,Close 就會完成。並行調用 Read 或並行調用 Write 也同樣安全:同種類的調用將按順序進行控制。
正因為是同步的,因此不能在一個 goroutine 中進行讀和寫。
另外,對於管道的 close 方法(非 CloseWithError 時),err 會被置為 EOF。
1.14. Copy 和 CopyN 函數
Copy 函數的簽名:
func Copy(dst Writer, src Reader) (written int64, err error)
函數文檔:
Copy 將 src 復制到 dst,直到在 src 上到達 EOF 或發生錯誤。它返回復制的字節數,如果有錯誤的話,還會返回在復制時遇到的第一個錯誤。
成功的 Copy 返回 err == nil,而非 err == EOF。由於 Copy 被定義為從 src 讀取直到 EOF 為止,因此它不會將來自 Read 的 EOF 當做錯誤來報告。
若 dst 實現了 ReaderFrom 接口,其復制操作可通過調用 dst.ReadFrom(src) 實現。此外,若 src 實現了 WriterTo 接口,其復制操作可通過調用 src.WriteTo(dst) 實現。
代碼:
io.Copy(os.Stdout, strings.NewReader("Go語言中文網"))
直接將內容輸出(寫入 Stdout 中)。
我們甚至可以這么做:
package main import ( "fmt" "io" "os" ) func main() { io.Copy(os.Stdout, os.Stdin) fmt.Println("Got EOF -- bye") }
執行:echo "Hello, World" | go run main.go
CopyN 函數的簽名:
func CopyN(dst Writer, src Reader, n int64) (written int64, err error)
函數文檔:
CopyN 將 n 個字節(或到一個error)從 src 復制到 dst。 它返回復制的字節數以及在復制時遇到的最早的錯誤。當且僅當err == nil時,written == n 。
若 dst 實現了 ReaderFrom 接口,復制操作也就會使用它來實現。
代碼:
io.CopyN(os.Stdout, strings.NewReader("Go語言中文網"), 8)
會輸出:
Go語言
1.15. ReadAtLeast 和 ReadFull 函數
ReadAtLeast 函數的簽名:
func ReadAtLeast(r Reader, buf []byte, min int) (n int, err error)
函數文檔:
ReadAtLeast 將 r 讀取到 buf 中,直到讀了最少 min 個字節為止。它返回復制的字節數,如果讀取的字節較少,還會返回一個錯誤。若沒有讀取到字節,錯誤就只是 EOF。如果一個 EOF 發生在讀取了少於 min 個字節之后,ReadAtLeast 就會返回 ErrUnexpectedEOF。若 min 大於 buf 的長度,ReadAtLeast 就會返回 ErrShortBuffer。對於返回值,當且僅當 err == nil 時,才有 n >= min。
一般可能不太會用到這個函數。使用時需要注意返回的 error 判斷。
ReadFull 函數的簽名:
func ReadFull(r Reader, buf []byte) (n int, err error)
函數文檔:
ReadFull 精確地從 r 中將 len(buf) 個字節讀取到 buf 中。它返回復制的字節數,如果讀取的字節較少,還會返回一個錯誤。若沒有讀取到字節,錯誤就只是 EOF。如果一個 EOF 發生在讀取了一些但不是所有的字節后,ReadFull 就會返回 ErrUnexpectedEOF。對於返回值,當且僅當 err == nil 時,才有 n == len(buf)。
注意該函數和 ReadAtLeast 的區別:ReadFull 將 buf 讀滿;而 ReadAtLeast 是最少讀取 min 個字節。
1.16. WriteString 函數
這是為了方便寫入 string 類型提供的函數,函數簽名:
func WriteString(w Writer, s string) (n int, err error)
函數文檔:
WriteString 將s的內容寫入w中,當 w 實現了 WriteString 方法時,會直接調用該方法,否則執行 w.Write([]byte(s))。
1.17. MultiReader 和 MultiWriter 函數
這兩個函數的定義分別是:
func MultiReader(readers ...Reader) Reader func MultiWriter(writers ...Writer) Writer
它們接收多個 Reader 或 Writer,返回一個 Reader 或 Writer。我們可以猜想到這兩個函數就是操作多個 Reader 或 Writer 就像操作一個。
事實上,在 io 包中定義了兩個非導出類型:mutilReader 和 multiWriter,它們分別實現了 io.Reader 和 io.Writer 接口。類型定義為:
type multiReader struct { readers []Reader } type multiWriter struct { writers []Writer }
對於這兩種類型對應的實現方法(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
代碼中首先構造了一個 io.Reader 的 slice,由 strings.Reader 和 bytes.Buffer 兩個實例組成,然后通過 MultiReader 得到新的 Reader,循環讀取新 Reader 中的內容。從輸出結果可以看到,第一次調用 Reader 的 Read 方法獲取到的是 slice 中第一個元素的內容……也就是說,MultiReader 只是邏輯上將多個 Reader 組合起來,並不能通過調用一次 Read 方法獲取所有 Reader 的內容。在所有的 Reader 內容都被讀完后,Reader 會返回 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語言中文網"))
這段程序執行后在生成 tmp.txt 文件,同時在文件和屏幕中都輸出:Go語言中文網
。這和 Unix 中的 tee 命令類似。
動手試試
Go 實現 Unix 中 tee 命令的功能很簡單吧。MultiWriter 的 Write 方法是如何實現的?有興趣可以自己實現一個,然后對着源碼比較一下。
1.18. TeeReader函數
函數簽名如下:
func TeeReader(r Reader, w Writer) Reader
TeeReader 返回一個 Reader,它將從 r 中讀到的數據寫入 w 中。所有經由它處理的從 r 的讀取都匹配於對應的對 w 的寫入。它沒有內部緩存,即寫入必須在讀取完成前完成。任何在寫入時遇到的錯誤都將作為讀取錯誤返回。
也就是說,我們通過 Reader 讀取內容后,會自動寫入到 Writer 中去。例子代碼如下:
reader := io.TeeReader(strings.NewReader("Go語言中文網"), os.Stdout) reader.Read(make([]byte, 20))
輸出結果:
Go語言中文網
這種功能的實現其實挺簡單,無非是在 Read 完后執行 Write。
至此,io 所有接口、類型和函數都講解完成。