# 常見的 I/O 接口
Go提供了一系列的 I/O 接口并經常在標準庫源碼中使用它們。盡可能使用這些接口而非傳遞結構或其它類型是Go推薦的方式(*<small>不必恐懼,Go中的接口并不像Java那樣浩如煙海,你喝杯咖啡的功夫就能把這幾個為數不多的接口翻來覆去的看很多遍</small>*)。其中,當屬 io.Reader 和 io.Writer 這兩個接口最為常用。在標準庫源碼中處處有他們倆的身影,熟悉并了解他們對于日常工作非常必要。
Reader 和 Writer 接口的定義是這樣的:
```
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
```
在Go中可以輕松的組合接口,看看下面的代碼:
```
type Seeker interface {
Seek(offset int64, whence int) (int64, error)
}
type ReadSeeker interface {
Reader
Seeker
}
```
這種使用方式你同樣可以在 io 包的 Pipe() 中看到:
```
func Pipe() (*PipeReader, *PipeWriter)
```
接下來讓我們看看該如何使用。
### 準備
根據以下步驟配置你的開發環境:
1. 從Go[國內官網](https://golang.google.cn/dl/)下載并安裝Go到你的操作系統中,配置GOPATH環境變量(*<small>[Go環境變量配置教程](https://slowbirdgogogo.com/install_simple)</small>*)。
2. 打開命令行,跳轉到你的 GOPATH/src 文件夾內,建立項目文件夾,例如$GOPATH/src/github.com/yourusername/customrepo。所有的代碼會在該目錄下進行修改。
3. 或者,你可以通過以下名直接安裝本書示例代碼:```go get github.com/agtorre/go-cookbook/```
### 實踐
以下步驟包括編寫和運行程序:
1. 在你的命令行中建立新的文件夾,名為chapter1/interfaces。
2. 跳轉到該文件夾。
3. 建立interfaces.go:
```
package interfaces
import (
"fmt"
"io"
"os"
)
// Copy直接將數據從in復制到out
// 它使用了buffer作為緩存
// 同時會將數據復制到Stdout
func Copy(in io.ReadSeeker, out io.Writer) error {
// 我們將結果寫入 out 和 Stdout
w := io.MultiWriter(out, os.Stdout)
// 標準的 io.Copy 調用,如果傳入的數據 in 過大會很危險
// 第一次向w復制數據
if _, err := io.Copy(w, in); err != nil {
return err
}
in.Seek(0, 0)
// 使用64字節作為緩存寫入 w
// 第二次向w復制數據
buf := make([]byte, 64)
if _, err := io.CopyBuffer(w, in, buf); err != nil {
return err
}
// 打印換行
fmt.Println()
return nil
}
```
4. 建立pipes.go。[理解golang io.Pipe](https://www.jianshu.com/p/aa207155ca7d)這篇文章將r和w分別比喻為在對方干活時一直睡覺的工人,形象生動,令人拍案叫絕:
```
package interfaces
import (
"io"
"os"
)
// PipeExample展現了對 io 接口的更多用法
func PipeExample() error {
// io.Pipe()返回的r和w分別實現了io.Reader 和 io.Writer接口
r, w := io.Pipe()
// 在w進行寫入的時候,r會發生阻塞
go func() {
// 這里我們簡單的寫入字符串,同樣也可以寫入json或base64編碼的其他東西
w.Write([]byte("test\n"))
w.Close()
}()
if _, err := io.Copy(os.Stdout, r); err != nil {
return err
}
return nil
}
```
5. 建立example文件夾,在文件夾內建立main.go:
```
package main
import (
"bytes"
"fmt"
"github.com/agtorre/go-cookbook/chapter1//interfaces"
)
func main() {
in := bytes.NewReader([]byte("example"))
out := &bytes.Buffer{}
fmt.Print("stdout on Copy = ")
if err := interfaces.Copy(in, out); err != nil {
panic(err)
}
fmt.Println("out bytes buffer =", out.String())
fmt.Print("stdout on PipeExample = ")
if err := interfaces.PipeExample(); err != nil {
panic(err)
}
}
```
6. 執行 go run main.go
7. 這會輸出:
```
stdout on Copy = exampleexample
out bytes buffer = exampleexample
stdout on PipeExample = test
```
8. 如果復制或編寫了自己的測試,請跳轉至上一個目錄并運行測試,并確保所有測試都通過。
### 說明
Copy函數在接口之間進行復制并將它們視為流。 將數據視為流的方式有很多實際用途,特別是在處理網絡或文件系統時。Copy函數還創建了一個復合寫入器,它將兩個寫入器流組合起來,并使用ReadSeeker寫入兩次。還有一個使用緩沖寫入的例子,如果你的內存不滿足數據流大小,那么使用它會很合適。
PipeReader和PipeWriter結構實現了io.Reader和io.Writer接口。他們連接在一起,從屬于一個內存管道。管道的主要目的是從流中讀取數據,同時從同一個數據流寫入不同的數據源。實質上,它將兩個流合并為一個管道。
Go接口使用了清晰的抽象來包裝常見操作的數據。這使 I/O 操作變得很明顯,所以使用io包學習接口組合很不錯。 pip包通常使用較少,但在連接輸入和輸出流時提供了極高的靈活性和線程安全性。
* * * *
學識淺薄,錯誤在所難免。歡迎在群中就本書提出修改意見,以饗后來者,長風拜謝。
Golang中國(211938256)
beego實戰(258969317)
Go實踐(386056972)
- 前言
- 第一章 I/O和文件系統
- 常見 I/O 接口
- 使用bytes和strings包
- 操作文件夾和文件
- 使用CSV格式化數據
- 操作臨時文件
- 使用 text/template和HTML/templates包
- 第二章 命令行工具
- 解析命令行flag標識
- 解析命令行參數
- 讀取和設置環境變量
- 操作TOML,YAML和JSON配置文件
- 操做Unix系統下的pipe管道
- 處理信號量
- ANSI命令行著色
- 第三章 數據類型轉換和解析
- 數據類型和接口轉換
- 使用math包和math/big包處理數字類型
- 貨幣轉換和float64注意事項
- 使用指針和SQL Null類型進行編碼和解碼
- 對Go數據編碼和解碼
- Go中的結構體標簽和反射
- 通過閉包實現集合操作
- 第四章 錯誤處理
- 錯誤接口
- 使用第三方errors包
- 使用log包記錄錯誤
- 結構化日志記錄
- 使用context包進行日志記錄
- 使用包級全局變量
- 處理恐慌
- 第五章 數據存儲
- 使用database/sql包操作MySQL
- 執行數據庫事務接口
- SQL的連接池速率限制和超時
- 操作Redis
- 操作MongoDB
- 創建存儲接口以實現數據可移植性
- 第六章 Web客戶端和APIs
- 使用http.Client
- 調用REST API
- 并發操作客戶端請求
- 使用OAuth2
- 實現OAuth2令牌存儲接口
- 封裝http請求客戶端
- 理解GRPC的使用
- 第七章 網絡服務
- 處理Web請求
- 使用閉包進行狀態處理
- 請求參數驗證
- 內容渲染
- 使用中間件
- 構建反向代理
- 將GRPC導出為JSON API
- 第八章 測試
- 使用標準庫進行模擬
- 使用Mockgen包
- 使用表驅動測試
- 使用第三方測試工具
- 模糊測試
- 行為驅動測試
- 第九章 并發和并行
- 第十章 分布式系統
- 第十一章 響應式編程和數據流
- 第十二章 無服務器編程
- 第十三章 性能改進