io包中最重要的是兩個接口:Reader和Writer
Reader接口#####
type Reader interface {
Read(p []byte) (n int ,err error)
}```
#####官方文檔中關于該接口方法的說明#####
>Read 將 len(p) 個字節讀取到 p 中。它返回讀取的字節數 n(0 <= n <= len(p)) 以及任何遇到的錯誤。即使 Read 返回的 n < len(p),它也會在調用過程中使用 p 的全部作為暫存空間。若一些數據可用但不到 len(p) 個字節,Read 會照例返回可用的數據,而不是等待更多數據。
> Read 在成功讀取 n > 0 個字節后遇到一個錯誤或 ```EOF (end-of-file)```,它就會返回讀取的字節數。它會從相同的調用中返回(非nil的)錯誤或從隨后的調用中返回錯誤(同時 n == 0)。 一般情況的一個例子就是 Reader 在輸入流結束時會返回一個非零的字節數,同時返回的 ```err``` 不是 ```EOF``` 就是```nil```。無論如何,下一個 Read 都應當返回 ```0, EOF```。
>調用者應當總在考慮到錯誤 err 前處理 n > 0 的字節。這樣做可以在讀取一些字節,以及允許的 EOF 行為后正確地處理 I/O 錯誤
*PS: 當```Read```方法返回錯誤時,不代表沒有讀取到任何數據,可能是數據被讀完了時返回的```io.EOF```。*
Reader 接口的方法集([Method_sets](http://golang.org/ref/spec#Method_sets))只包含一個 Read 方法,因此,所有實現了 ```Read``` 方法的類型都實現了```io.Reader ```接口,也就是說,在所有需要``` io.Reader``` 的地方,可以傳遞實現了 ````Read()``` 方法的類型的實例。
#####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 的錯誤。
io.File#####
os.File同時實現了io.Reader
和io.Writer
接口。
在os包中有三個可倒出的特殊文件(os.File
)實例:Stdin
、Stdout
和Stderr
,自然也實現了 io.Reader
和 io.Writer
.
實現了 io.Reader 或 io.Writer 接口的類型#####
-
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
-
io.LimitedReader、io.PipeReader、io.SectionReader
實現了io.Reader
-
io.PipeWriter
實現了io.Writer
PS: Go接口的命名約定:接口名以 er 結尾。注意,這里并非強行要求,你完全可以不以 er 結尾。標準庫中有些接口也不是以 er 結尾的。
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 返回,那么這時 err == EOF 或者 err == nil。
>若 ReadAt 按查找偏移量從輸入源讀取,ReadAt 應當既不影響
基本查找偏移量也不被它所影響。
>ReadAt 的客戶端可對相同的輸入源并行執行 ReadAt 調用。
######io.WriterAt 接口######
---
type Writer interface {
WriterAt(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 調用。
ReaderFrom 和 WriterTo 接口#####
ReaderFrom######
type ReaderFrom interface {
ReaderFrom(r Reader) (n int64, err error)
}```
官方文檔說明:
>ReadFrom 從 r 中讀取數據,直到 EOF 或發生錯誤。其返回值 n 為讀取的字節數。除 io.EOF 之外,在讀取過程中遇到的任何錯誤也將被返回。
>如果 ReaderFrom 可用,Copy 函數就會使用它。
*PS: ```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方法實現。
如果不通過 ReadFrom 接口來做這件事,而是使用 io.Reader 接口,我們有兩種思路:######
- 先獲取文件的大小(File 的 Stat 方法),之后定義一個該大小的 []byte,通過 Read 一次性讀取
- 定義一個小的 []byte,不斷的調用 Read 方法直到遇到 EOF,將所有讀取到的 []byte 連接到一起
WriterTo#####
type WriterTo interface {
WriterTo(w Writer) (n int64, err error)
}```
官方文檔說明
>WriteTo 將數據寫入 w 中,直到沒有數據可寫或發生錯誤。其返回值 n 為寫入的字節數。 在寫入過程中遇到的任何錯誤也將被返回。
>如果 WriterTo 可用,Copy 函數就會使用它
如果有“一次性從某個地方讀或寫到某個地方去。”這樣的需求,可以考慮使用這兩個接口:``` io.ReaderFrom``` 和 ```io.WriterTo```.
#####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, os.SEEK_END)
r, _, _ := reader.ReadRune()
fmt.Printf("%c\n", r)```
whence 的值,在 os 包中定義了相應的常量
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
}```
該接口只有一個 Close() 方法,用于關閉數據流。
#####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
eg:
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("寫入錯誤")
}```
接口的使用.... 在二進制數據或歸檔壓縮時用的比較多
#####ByteScanner、RuneReader 和 RuneScanner#####
######ByteScanner 接口的定義:######
------
type ByteScanner interface {
ByteReader // 內嵌了 ByteReader 接口
UnreadByte() error
}```
UnreadByte 方法的意思是:將上一次 ReadByte 的字節還原,使得再次調用 ReadByte 返回的結果和上一次調用相同,也就是說,UnreadByte 是重置上一次的 ReadByte。注意,UnreadByte 調用之前必須調用了 ReadByte,且不能連續調用 UnreadByte。
RuneReader 接口和 ByteReader 類似,只是 ReadRune 方法讀取單個 UTF-8 字符,返回其 rune 和該字符占用的字節數。該接口在 [regexp](http://golang.org/pkg/rege
xp) 包有用到。
RuneScanner 接口和 ByteScanner 類似
ReadCloser、ReadSeeker、ReadWriteCloser、ReadWriteSeeker、ReadWriter、WriteCloser 和 WriteSeeker 接口#####
這些接口是上面介紹的接口的兩個或三個組合而成的新接口。例如 ReadWriter 接口:
type ReadWriter interface {
Reader
Writer
}```
這是 Reader 接口和 Writer 接口的簡單組合(內嵌)。
這些接口的作用是:有些時候同時需要某兩個接口的所有功能,即必須同時實現了某兩個接口的類型才能夠被傳入使用。可見,io 包中有大量的“小接口”,這樣方便組合為“大接口”。
######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 的功能。
######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} }```
######PipeReader 和 PipeWriter 類型######
PipeReader(一個沒有任何導出字段的 struct)是管道的讀取端。它實現了 io.Reader 和 io.Closer 接口。
關于 Read 方法的說明:從管道中讀取數據。該方法會堵塞,直到管道寫入端開始寫入數據或寫入端關閉了。如果寫入端關閉時帶上了 error(即調用 CloseWithError 關閉),該方法返回的 err 就是寫入端傳遞的error;否則 err 為 EOF。
PipeWriter(一個沒有任何導出字段的 struct)是管道的寫入端。它實現了 io.Writer 和 io.Closer 接口。
關于 Write 方法的說明:寫數據到管道中。該方法會堵塞,直到管道讀取端讀完所有數據或讀取端關閉了。如果讀取端關閉時帶上了 error(即調用 CloseWithError 關閉),該方法返回的 err 就是讀取端傳遞的error;否則 err 為 ErrClosedPipe。
######io 包 管道 (pipe) 源碼分析######
PipeWriter 和 PipeReader 都只有i一個不可導出的成員```p *pipe```,這兩種類型的所有方法都是調用了 pipe 類型對應的方法實現的。
pipe類型的定義:
// A pipe is the shared pipe structure underlying PipeReader and PipeWriter.
type pipe struct {
rl sync.Mutex // gates readers one at a time
wl sync.Mutex // gates writers one at a time
l sync.Mutex // protects remaining fields
data []byte // data remaining in pending write
rwait sync.Cond // waiting reader
wwait sync.Cond // waiting writer
rerr error // if reader closed, error to give writes
werr error // if writer closed, error to give reads
}```
字段說明:
- rl/wl 用于控制同一時刻只能有一個讀取器或寫入器
- l 用于保護其他字段
- data 在管道中的數據
- rwait/wwait sync.Cond 類型(后續會講解),分別控制讀取器或寫入器等待
- rerr/werr 讀取器(寫入器)關閉,該錯誤會被 Write (Read) 方法返回
.
.
.
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) 實現。
eg1:
io.Copy(os.Stdout, strings.NewReader("Hello World!))```
**eg2:**
// 直接將內容輸出(寫入 Stdout 中)
func main() {
io.Copy(os.Stdout, os.Stdin)
fmt.Println("Got EOF -- bye")
}```
CopyN 函數的簽名:######
func CopyN(dst Writer, src Reader, n int64) (written int64, err error)```
函數文檔:
>CopyN 將 n 個字節從 src 復制到 dst。 它返回復制的字節數以及在復制時遇到的最早的錯誤。由于 Read 可以返回要求的全部數量及一個錯誤(包括 EOF),因此 CopyN 也能如此。
若 dst 實現了 ReaderFrom 接口,復制操作也就會使用它來實現。
#####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 個字節。
#####WriteString 函數#####
---
func WriteString(w Writer, s string) (n int, err error)```
當 w 實現了 WriteString 方法時,直接調用該方法,否則執行 w.Write([]byte(s))。
MultiReader 和 MultiWriter 函數#####
在 io 包中定義了兩個非導出類型:mutilReader 和 multiWriter,它們分別實現了 io.Reader 和 io.Writer 接口。類型定義為:
type multiReader struct {
readers []Reader
}
type multiWriter struct {
writers []Writer
}```
MultiReader 和 MultiWriter 定義:
func MultiReader(readers ...Reader) Reader
func MultiWriter(writers ...Writer) Writer```
它們接收多個 Reader 或 Writer,返回一個 Reader 或 Writer。
MultiReader 只是邏輯上將多個 Reader 組合起來,并不能通過調用一次 Read 方法獲取所有 Reader 的內容。在所有的 Reader 內容都被讀完后,Reader 會返回 EOF。
TeeReader函數#####
函數簽名如下:
func TeeReader(r Reader, w Writer) Reader```
TeeReader 返回一個 Reader,它將從 r 中讀到的數據寫入 w 中。所有經由它處理的從 r 的讀取都匹配于對應的對 w 的寫入。它沒有內部緩存,即寫入必須在讀取完成前完成。任何在寫入時遇到的錯誤都將作為讀取錯誤返回。
也就是說,我們通過 Reader 讀取內容后,會自動寫入到 Writer 中去。例子代碼如下:
reader := io.TeeReader(strings.NewReader("Hello Go!"), os.Stdout)reader.Read(make([]byte, 9))```
輸出結果:
Hello Go!```
這種功能的實現其實挺簡單,無非是在 Read 完后執行 Write。