Go 語言標(biāo)準(zhǔn)庫 io 包內(nèi)有一些常用接口和方法,本文配合圖片和實(shí)際代碼,詳細(xì)介紹了 io 包。
前言
在 Go 中,輸入和輸出操作是使用原語實(shí)現(xiàn)的,這些原語將數(shù)據(jù)模擬成可讀的或可寫的字節(jié)流。
為此,Go 的 io 包提供了 io.Reader 和 io.Writer 接口,分別用于數(shù)據(jù)的輸入和輸出
Go 官方提供了一些 API,支持對內(nèi)存結(jié)構(gòu),文件,網(wǎng)絡(luò)連接等資源進(jìn)行操作
本文重點(diǎn)介紹如何實(shí)現(xiàn)標(biāo)準(zhǔn)庫中 io.Reader 和 io.Writer 兩個(gè)接口,來完成流式傳輸數(shù)據(jù)。
io.Reader
io.Reader 表示一個(gè)讀取器,它將數(shù)據(jù)從某個(gè)資源讀取到傳輸緩沖區(qū)。在緩沖區(qū)中,數(shù)據(jù)可以被流式傳輸和使用。
對于要用作讀取器的類型,它必須實(shí)現(xiàn) io.Reader 接口的唯一一個(gè)方法 Read(p []byte)。
換句話說,只要實(shí)現(xiàn)了 Read(p []byte) ,那它就是一個(gè)讀取器。
type Reader interface {
Read(p []byte) (n int, err error)
}
Read() 方法有兩個(gè)返回值,一個(gè)是讀取到的字節(jié)數(shù),一個(gè)是發(fā)生錯(cuò)誤時(shí)的錯(cuò)誤。
同時(shí),如果資源內(nèi)容已全部讀取完畢,應(yīng)該返回 io.EOF 錯(cuò)誤。
使用 Reader
利用 Reader 可以很容易地進(jìn)行流式數(shù)據(jù)傳輸。Reader 方法內(nèi)部是被循環(huán)調(diào)用的,每次迭代,它會(huì)從數(shù)據(jù)源讀取一塊數(shù)據(jù)放入緩沖區(qū) p (即 Read 的參數(shù) p)中,直到返回 io.EOF 錯(cuò)誤時(shí)停止。
下面是一個(gè)簡單的例子,通過 string.NewReader(string) 創(chuàng)建一個(gè)字符串讀取器,然后流式地按字節(jié)讀?。?/p>
func main() {
reader := strings.NewReader(“Clear is better than clever”)
p := make([]byte, 4)
for {
n, err := reader.Read(p)
if err != nil{
if err == io.EOF {
fmt.Println(“EOF:”, n)
break
}
fmt.Println(err)
os.Exit(1)
}
fmt.Println(n, string(p[:n]))
}
}
輸出打印的內(nèi)容:
4 Clea
4 r is
4 bet
4 ter
4 than
4 cle
3 ver
EOF: 0
可以看到,最后一次返回的 n 值有可能小于緩沖區(qū)大小。
自己實(shí)現(xiàn)一個(gè) Reader
上一節(jié)是使用標(biāo)準(zhǔn)庫中的 io.Reader 讀取器實(shí)現(xiàn)的。
現(xiàn)在,讓我們看看如何自己實(shí)現(xiàn)一個(gè)。它的功能是從流中過濾掉非字母字符。
type alphaReader struct {
// 資源
src string
// 當(dāng)前讀取到的位置
cur int
}
// 創(chuàng)建一個(gè)實(shí)例func newAlphaReader(src string) *alphaReader {
return &alphaReader{src: src}
}
// 過濾函數(shù)func alpha(r byte) byte {
if (r 》= ‘A’ && r 《= ‘Z’) || (r 》= ‘a(chǎn)’ && r 《= ‘z’) {
return r
}
return 0
}
// Read 方法func (a *alphaReader) Read(p []byte) (int, error) {
// 當(dāng)前位置 》= 字符串長度 說明已經(jīng)讀取到結(jié)尾 返回 EOF
if a.cur 》= len(a.src) {
return 0, io.EOF
}
// x 是剩余未讀取的長度
x := len(a.src) - a.cur
n, bound := 0, 0
if x 》= len(p) {
// 剩余長度超過緩沖區(qū)大小,說明本次可完全填滿緩沖區(qū)
bound = len(p)
} else if x 《 len(p) {
// 剩余長度小于緩沖區(qū)大小,使用剩余長度輸出,緩沖區(qū)不補(bǔ)滿
bound = x
}
buf := make([]byte, bound)
for n 《 bound {
// 每次讀取一個(gè)字節(jié),執(zhí)行過濾函數(shù)
if char := alpha(a.src[a.cur]); char != 0 {
buf[n] = char
}
n++
a.cur++
}
// 將處理后得到的 buf 內(nèi)容復(fù)制到 p 中
copy(p, buf)
return n, nil
}
func main() {
reader := newAlphaReader(“Hello! It‘s 9am, where is the sun?”)
p := make([]byte, 4)
for {
n, err := reader.Read(p)
if err == io.EOF {
break
}
fmt.Print(string(p[:n]))
}
fmt.Println()
}
輸出打印的內(nèi)容:
HelloItsamwhereisthesun
組合多個(gè) Reader,目的是重用和屏蔽下層實(shí)現(xiàn)的復(fù)雜度
標(biāo)準(zhǔn)庫已經(jīng)實(shí)現(xiàn)了許多 Reader。
使用一個(gè) Reader 作為另一個(gè) Reader 的實(shí)現(xiàn)是一種常見的用法。
這樣做可以讓一個(gè) Reader 重用另一個(gè) Reader 的邏輯,下面展示通過更新 alphaReader 以接受 io.Reader 作為其來源。
type alphaReader struct {
// alphaReader 里組合了標(biāo)準(zhǔn)庫的 io.Reader
reader io.Reader
}
func newAlphaReader(reader io.Reader) *alphaReader {
return &alphaReader{reader: reader}
}
func alpha(r byte) byte {
if (r 》= ’A‘ && r 《= ’Z‘) || (r 》= ’a‘ && r 《= ’z‘) {
return r
}
return 0
}
func (a *alphaReader) Read(p []byte) (int, error) {
// 這行代碼調(diào)用的就是 io.Reader
n, err := a.reader.Read(p)
if err != nil {
return n, err
}
buf := make([]byte, n)
for i := 0; i 《 n; i++ {
if char := alpha(p[i]); char != 0 {
buf[i] = char
}
}
copy(p, buf)
return n, nil
}
func main() {
// 使用實(shí)現(xiàn)了標(biāo)準(zhǔn)庫 io.Reader 接口的 strings.Reader 作為實(shí)現(xiàn)
reader := newAlphaReader(strings.NewReader(“Hello! It’s 9am, where is the sun?”))
p := make([]byte, 4)
for {
n, err := reader.Read(p)
if err == io.EOF {
break
}
fmt.Print(string(p[:n]))
}
fmt.Println()
}
這樣做的另一個(gè)優(yōu)點(diǎn)是 alphaReader 能夠從任何 Reader 實(shí)現(xiàn)中讀取。
例如,以下代碼展示了 alphaReader 如何與 os.File 結(jié)合以過濾掉文件中的非字母字符:
func main() {
// file 也實(shí)現(xiàn)了 io.Reader
file, err := os.Open(“。/alpha_reader3.go”)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
defer file.Close()
// 任何實(shí)現(xiàn)了 io.Reader 的類型都可以傳入 newAlphaReader
// 至于具體如何讀取文件,那是標(biāo)準(zhǔn)庫已經(jīng)實(shí)現(xiàn)了的,我們不用再做一遍,達(dá)到了重用的目的
reader := newAlphaReader(file)
p := make([]byte, 4)
for {
n, err := reader.Read(p)
if err == io.EOF {
break
}
fmt.Print(string(p[:n]))
}
fmt.Println()
}
io.Writer
io.Writer 表示一個(gè)編寫器,它從緩沖區(qū)讀取數(shù)據(jù),并將數(shù)據(jù)寫入目標(biāo)資源。
對于要用作編寫器的類型,必須實(shí)現(xiàn) io.Writer 接口的唯一一個(gè)方法 Write(p []byte)
同樣,只要實(shí)現(xiàn)了 Write(p []byte) ,那它就是一個(gè)編寫器。
type Writer interface {
Write(p []byte) (n int, err error)
}
Write() 方法有兩個(gè)返回值,一個(gè)是寫入到目標(biāo)資源的字節(jié)數(shù),一個(gè)是發(fā)生錯(cuò)誤時(shí)的錯(cuò)誤。
使用 Writer
標(biāo)準(zhǔn)庫提供了許多已經(jīng)實(shí)現(xiàn)了 io.Writer 的類型。
下面是一個(gè)簡單的例子,它使用 bytes.Buffer 類型作為 io.Writer 將數(shù)據(jù)寫入內(nèi)存緩沖區(qū)。
func main() {
proverbs := []string{
“Channels orchestrate mutexes serialize”,
“Cgo is not Go”,
“Errors are values”,
“Don‘t panic”,
}
var writer bytes.Buffer
for _, p := range proverbs {
n, err := writer.Write([]byte(p))
if err != nil {
fmt.Println(err)
os.Exit(1)
}
if n != len(p) {
fmt.Println(“failed to write data”)
os.Exit(1)
}
}
fmt.Println(writer.String())
}
輸出打印的內(nèi)容:
Channels orchestrate mutexes serializeCgo is not GoErrors are valuesDon’t panic
自己實(shí)現(xiàn)一個(gè) Writer
下面我們來實(shí)現(xiàn)一個(gè)名為 chanWriter 的自定義 io.Writer ,它將其內(nèi)容作為字節(jié)序列寫入 channel 。
type chanWriter struct {
// ch 實(shí)際上就是目標(biāo)資源
ch chan byte
}
func newChanWriter() *chanWriter {
return &chanWriter{make(chan byte, 1024)}
}
func (w *chanWriter) Chan() 《-chan byte {
return w.ch
}
func (w *chanWriter) Write(p []byte) (int, error) {
n := 0
// 遍歷輸入數(shù)據(jù),按字節(jié)寫入目標(biāo)資源
for _, b := range p {
w.ch 《- b
n++
}
return n, nil
}
func (w *chanWriter) Close() error {
close(w.ch)
return nil
}
func main() {
writer := newChanWriter()
go func() {
defer writer.Close()
writer.Write([]byte(“Stream ”))
writer.Write([]byte(“me!”))
}()
for c := range writer.Chan() {
fmt.Printf(“%c”, c)
}
fmt.Println()
}
要使用這個(gè) Writer,只需在函數(shù) main() 中調(diào)用 writer.Write()(在單獨(dú)的 goroutine 中)。
因?yàn)?chanWriter 還實(shí)現(xiàn)了接口 io.Closer ,所以調(diào)用方法 writer.Close() 來正確地關(guān)閉 channel,以避免發(fā)生泄漏和死鎖。
io 包里其他有用的類型和方法
如前所述,Go 標(biāo)準(zhǔn)庫附帶了許多有用的功能和類型,讓我們可以輕松使用流式 io。
os.File
類型 os.File 表示本地系統(tǒng)上的文件。它實(shí)現(xiàn)了 io.Reader 和 io.Writer ,因此可以在任何 io 上下文中使用。
例如,下面的例子展示如何將連續(xù)的字符串切片直接寫入文件:
func main() {
proverbs := []string{
“Channels orchestrate mutexes serialize
”,
“Cgo is not Go
”,
“Errors are values
”,
“Don‘t panic
”,
}
file, err := os.Create(“。/proverbs.txt”)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
defer file.Close()
for _, p := range proverbs {
// file 類型實(shí)現(xiàn)了 io.Writer
n, err := file.Write([]byte(p))
if err != nil {
fmt.Println(err)
os.Exit(1)
}
if n != len(p) {
fmt.Println(“failed to write data”)
os.Exit(1)
}
}
fmt.Println(“file write done”)
}
同時(shí),io.File 也可以用作讀取器來從本地文件系統(tǒng)讀取文件的內(nèi)容。
例如,下面的例子展示了如何讀取文件并打印其內(nèi)容:
func main() {
file, err := os.Open(“。/proverbs.txt”)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
defer file.Close()
p := make([]byte, 4)
for {
n, err := file.Read(p)
if err == io.EOF {
break
}
fmt.Print(string(p[:n]))
}
}
標(biāo)準(zhǔn)輸入、輸出和錯(cuò)誤
os 包有三個(gè)可用變量 os.Stdout ,os.Stdin 和 os.Stderr ,它們的類型為 *os.File,分別代表 系統(tǒng)標(biāo)準(zhǔn)輸入,系統(tǒng)標(biāo)準(zhǔn)輸出 和 系統(tǒng)標(biāo)準(zhǔn)錯(cuò)誤 的文件句柄。
例如,下面的代碼直接打印到標(biāo)準(zhǔn)輸出:
func main() {
proverbs := []string{
“Channels orchestrate mutexes serialize
”,
“Cgo is not Go
”,
“Errors are values
”,
“Don’t panic
”,
}
for _, p := range proverbs {
// 因?yàn)?os.Stdout 也實(shí)現(xiàn)了 io.Writer
n, err := os.Stdout.Write([]byte(p))
if err != nil {
fmt.Println(err)
os.Exit(1)
}
if n != len(p) {
fmt.Println(“failed to write data”)
os.Exit(1)
}
}
}
io.Copy()
io.Copy() 可以輕松地將數(shù)據(jù)從一個(gè) Reader 拷貝到另一個(gè) Writer。
它抽象出 for 循環(huán)模式(我們上面已經(jīng)實(shí)現(xiàn)了)并正確處理 io.EOF 和 字節(jié)計(jì)數(shù)。
下面是我們之前實(shí)現(xiàn)的簡化版本:
func main() {
proverbs := new(bytes.Buffer)
proverbs.WriteString(“Channels orchestrate mutexes serialize
”)
proverbs.WriteString(“Cgo is not Go
”)
proverbs.WriteString(“Errors are values
”)
proverbs.WriteString(“Don‘t panic
”)
file, err := os.Create(“。/proverbs.txt”)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
defer file.Close()
// io.Copy 完成了從 proverbs 讀取數(shù)據(jù)并寫入 file 的流程
if _, err := io.Copy(file, proverbs); err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Println(“file created”)
}
那么,我們也可以使用 io.Copy() 函數(shù)重寫從文件讀取并打印到標(biāo)準(zhǔn)輸出的先前程序,如下所示:
func main() {
file, err := os.Open(“。/proverbs.txt”)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
defer file.Close()
if _, err := io.Copy(os.Stdout, file); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
io.WriteString()
此函數(shù)讓我們方便地將字符串類型寫入一個(gè) Writer:
func main() {
file, err := os.Create(“。/magic_msg.txt”)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
defer file.Close()
if _, err := io.WriteString(file, “Go is fun!”); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
使用管道的 Writer 和 Reader
類型 io.PipeWriter 和 io.PipeReader 在內(nèi)存管道中模擬 io 操作。
數(shù)據(jù)被寫入管道的一端,并使用單獨(dú)的 goroutine 在管道的另一端讀取。
下面使用 io.Pipe() 創(chuàng)建管道的 reader 和 writer,然后將數(shù)據(jù)從 proverbs 緩沖區(qū)復(fù)制到io.Stdout :
func main() {
proverbs := new(bytes.Buffer)
proverbs.WriteString(“Channels orchestrate mutexes serialize
”)
proverbs.WriteString(“Cgo is not Go
”)
proverbs.WriteString(“Errors are values
”)
proverbs.WriteString(“Don’t panic
”)
piper, pipew := io.Pipe()
// 將 proverbs 寫入 pipew 這一端
go func() {
defer pipew.Close()
io.Copy(pipew, proverbs)
}()
// 從另一端 piper 中讀取數(shù)據(jù)并拷貝到標(biāo)準(zhǔn)輸出
io.Copy(os.Stdout, piper)
piper.Close()
}
緩沖區(qū) io
標(biāo)準(zhǔn)庫中 bufio 包支持 緩沖區(qū) io 操作,可以輕松處理文本內(nèi)容。
例如,以下程序逐行讀取文件的內(nèi)容,并以值 ‘ ’ 分隔:
func main() {
file, err := os.Open(“。/planets.txt”)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
defer file.Close()
reader := bufio.NewReader(file)
for {
line, err := reader.ReadString(‘
’)
if err != nil {
if err == io.EOF {
break
} else {
fmt.Println(err)
os.Exit(1)
}
}
fmt.Print(line)
}
}
ioutil
io 包下面的一個(gè)子包 utilio 封裝了一些非常方便的功能
例如,下面使用函數(shù) ReadFile 將文件內(nèi)容加載到 []byte 中。
package main
import (
“io/ioutil”
。。。
)
func main() {
bytes, err := ioutil.ReadFile(“。/planets.txt”)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Printf(“%s”, bytes)
}
總結(jié)
本文介紹了如何使用 io.Reader 和 io.Writer 接口在程序中實(shí)現(xiàn)流式 IO。閱讀本文后,您應(yīng)該能夠了解如何使用 io 包來實(shí)現(xiàn) 流式傳輸 IO 數(shù)據(jù)的程序。
其中有一些例子,展示了如何創(chuàng)建自己的類型,并實(shí)現(xiàn)io.Reader 和 io.Writer 。
這是一個(gè)簡單介紹性質(zhì)的文章,沒有擴(kuò)展開來講。
例如,我們沒有深入文件 IO,緩沖 IO,網(wǎng)絡(luò) IO 或格式化 IO(保存用于將來的寫入)。
我希望這篇文章可以讓你了解 Go 語言中 流式 IO 的常見用法是什么。
謝謝!
轉(zhuǎn)自:ronniesong
segmentfault.com/a/1190000015591319
編輯:jq
-
Writer
+關(guān)注
關(guān)注
0文章
8瀏覽量
7347
原文標(biāo)題:Go 中 io 包的使用方法
文章出處:【微信號(hào):magedu-Linux,微信公眾號(hào):馬哥Linux運(yùn)維】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
IO-Link接口的功能和特點(diǎn)
λ-IO:存儲(chǔ)計(jì)算下的IO棧設(shè)計(jì)
![λ-<b class='flag-5'>IO</b>:存儲(chǔ)計(jì)算下的<b class='flag-5'>IO</b>棧設(shè)計(jì)](https://file1.elecfans.com/web3/M00/00/AF/wKgZO2dNHbaAIkp-AAAM3550zYU863.png)
MR20遠(yuǎn)程IO與IO-Link的差異化應(yīng)用
本地IO與遠(yuǎn)程IO:揭秘工業(yè)自動(dòng)化中的兩大關(guān)鍵角色
IO-Link的定義和特點(diǎn)
io口和串口的區(qū)別 單片機(jī)有多少個(gè)io口
MCU IO口的作用和特點(diǎn)
使用IO-link主幀處理程序實(shí)現(xiàn)靈活的時(shí)序配置
![使用<b class='flag-5'>IO</b>-link主幀處理<b class='flag-5'>程序</b><b class='flag-5'>實(shí)現(xiàn)</b>靈活的時(shí)序配置](https://file.elecfans.com/web1/M00/D9/4E/pIYBAF_1ac2Ac0EEAABDkS1IP1s689.png)
遠(yuǎn)程IO:實(shí)現(xiàn)設(shè)備間高效通信與控制的橋梁
![遠(yuǎn)程<b class='flag-5'>IO</b>:<b class='flag-5'>實(shí)現(xiàn)</b>設(shè)備間高效通信與控制的橋梁](https://file1.elecfans.com/web2/M00/05/8C/wKgZombaya-ADBZuAAJBvey4OvY489.png)
初識(shí)IO-Link及IO-Link設(shè)備軟件協(xié)議棧
![初識(shí)<b class='flag-5'>IO</b>-Link及<b class='flag-5'>IO</b>-Link設(shè)備軟件協(xié)議棧](https://file1.elecfans.com/web2/M00/F9/9D/wKgZomaLUNqAUDQ6AATSbLoa6xU520.png)
評論