io - 基本的IO接口
io 包为I/O原语提供了基本的接口. 它主要包装了这些原语的已有实现. 由于这些被接口包装的I/O原语是由不同的低级操作实现, 因此, 在另有声明之前不该假定他们的并行执行是安全的.
在io包中最重要的是两个接口: Reader和Writer 接口. 本文所提到的各种IO包, 都跟这两个接口有关, 也就是说, 只要满足这两个接口, 它就可以使用IO包的功能.
Reader 接口
Reader
接口的定义如下:
type Reader interface{
Reader(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_type), 我们知道Reader接口的方法集(Method_sets)只包含一个Reade方法, 因此, 所有实现了Read方法的类型都满足io.Reader接口, 也就是说, 在所有需要io.Reader的地方, 可以传递实现了Read()方法的类型的实例. 下面, 我们通过具体的例子来谈谈该接口的用法:
package main
import (
"fmt"
"io"
"os"
"strings"
)
func main() {
ReaderExample()
}
func ReaderExample() {
FOREND:
for {
readerMenu()
var ch string
fmt.Scanln(&ch)
var (
data []byte
err error
)
switch strings.ToLower(ch) {
case "1":
fmt.Println("请输入不多于9个字符, 以回车结束:")
data, err = ReadFrom(os.Stdin, 11)
case "2":
file, err :=os.Open("/Users/finup/GoglandProjects/src/demo/test.txt")
if err != nil {
fmt.Println("打开文件错误test.txt ", err)
continue
}
data, err = ReadFrom(file, 9)
file.Close()
case "3":
data, err = ReadFrom(strings.NewReader("from string"), 12)
case "4":
fmt.Println("暂未实现")
case "b":
fmt.Println("返回上级菜单")
break FOREND
case "q":
fmt.Println("程序退出")
os.Exit(0)
default:
fmt.Println("输入错误")
continue
}
if err != nil {
fmt.Println("数据读取失败, 可以试试从其他输入源读取!")
} else {
fmt.Println("读到的数据是:%s\n", data)
}
}
}
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
}
func readerMenu() {
fmt.Println(" ")
fmt.Println("*********从不同来源读取数据************")
fmt.Println("*********选择数据源, 请输入:***********")
fmt.Println("1 表示 标准输入")
fmt.Println("2 表示 普通文件")
fmt.Println("3 表示 从字符串")
fmt.Println("4 表示 从网络")
fmt.Println("q 退出")
fmt.Println("************************************")
}
ReadFrom
函数将io.Reader 作为参数, 也就是说, ReadFrom可以从任意的地方读取数据,只要来源实现了io.Reader接口.比如, 我们可以从标准输入, 文件, 字符串等读取数据, 示例代码如下:
// 从标准输入读取
data, err = ReadFrom(io.Stdin, 11)
// 从普通文件读取, 其中file 是os.File 的实例
data, err = ReadFrom(file, 9)
// 从字符串读取
data, err = ReadFrom(strings.NewReader("from string"), 12)
注意:
io.EOF
变量的定义:var EOF = errors.New("EOF")
, 是error类型. 根据reader接口的说明, 在n > 0
且数据被读完了的情况下, 当次返回的error 有可能是EOF 也有可能是nil.
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.Write接口.
在上个例子中, 我们是自己实现了一个函数接受一个io.Reader类型的参数. 这里, 我们通过标准库的例子来学习.
在fmt标准库中, 有一组函数: Fprint/Fprintf/Fprintln
, 它们接受一个io.Writer类型参数(第一个参数), 也就是说它们将数据格式化输出到io.Writer中. 那么, 调用这组函数时, 该如何传递这个参数呢?
我们以fmt.Fprintln为例, 同时看一下fmt.Println函数的源码.
// Println formats using the default formats for its operands and writes to standard output.
// Spaces are always added between operands and a newline is appended.
// It returns the number of bytes written and any write error encountered.
func Println(a ...interface{}) (n int, err error) {
return Fprintln(os.Stdout, a...)
}
很显然, fmt.Println会将内容输出到标准输出中. 后面我们将详细介绍fmt包.
实现了io.Reader接口或io.Writer 接口的类型
很多人看到函数参数是一个接口类型, 很多时候有些束手无策, 不知道该怎么传递参数. 还有人问: 标准库中有哪些类型实现了io.Reader
或io.Writer
接口?
通过本节上面的例子, 我们可以知道, os.File同时实现了这两个接口. 我们还看到os.Stdin/Stdout
这样的代码,它们似乎分别实现了io.Reader/io.Writer
接口. 没错, 实际上在os包中有这样的代码:
// Stdin, Stdout, and Stderr are open Files pointing to the standard input,
// standard output, and standard error file descriptors.
//
// Note that the Go runtime writes to standard error for panics and crashes;
// closing Stderr may cause those messages to go elsewhere, perhaps
// to a file opened later.
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). 以下列表列出了实现了相关接口的类型:
- os.File 同时实现 io.Reader 和 io.Writer
- strings.Reader 实现了io.Reader
- bufio.Reader/Writer 分别实现了io.Reader 和io.Writer
- bytes.Buffer同时实现了io.Reader 和io.Writer
- 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
- 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结尾的.
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
调用.
可见, ReadAt
接口使得可以从指定偏移量处开始读取数据.
简单示例代码如下:
package main
import (
"fmt"
"strings"
)
func main(){
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
实现了WriteAt
接口):
package main
import (
"fmt"
"os"
)
func main() {
file, err := os.Create("./writeAt.txt")
if err != nil {
panic(err)
}
defer file.Close()
file.WriteString("Golang测试wirteAt--这里是多余")
n, err := file.WriteAt([]byte("Go语言开发"), 21)
if err != nil {
panic(err)
}
fmt.Println(n)
}
打开文件writeAt.txt, 内容是:Golang测试wirteAt--Go语言开发
.
分析:
file.WriteString("Golang测试wirteAt--这里是多余")
往文件中写入Golang测试wirteAt--这里是多余
,之后
file.WriteAt([]byte("Go语言开发"), 21)
在文件流的offset=21
处写入Go语言开发
(会覆盖改位置的内容).
ReaderFrom 和 WriterTo 接口
ReaderFrom的定义如下:
type ReaderFrom interface {
ReadFrom(r Reader) (n int64, err error)
}
官方文档中关于该接口方法的说明:
ReadFrom
从r
中读取数据, 直到EOF或者发生错误. 其返回值n
为读取的字节数.除io.EOF外, 在读取过程中遇到的任何错误也将被返回. 如果ReadFrom
可用,Copy
函数就会使用它.
注意: ReadFrom
方法不会返回 err == EOF
.
下面的例子简单的实现将文件中的数据全部读取(显示在标准输出):
package main
import (
"bufio"
"os"
)
func main() {
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
方法实现, 会发现, 其实它们的实现和上面说的第二种思路类似.
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
接口的讲解, 对该接口的使用应该可以很好的掌握.
这里只提供简单的示例代码: 将一段文本输出到标准输出
package main
import (
"bytes"
"os"
)
func main(){
reader := bytes.NewReader([]byte("Go语言中文网"))
reader.WriteTo(os.Stdout)
}
通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
方法是用于设置偏移量的, 这样可以从某个特定位置开始操作数据流.听起来和Reader/WriterAt
接口有些类似,不过Seek
接口更加灵活, 可以更好的控制读写数据流的位置.
简单的示例代码: 获取倒数第二个字符(需要考虑UTF-8编码:utf-8编码一个汉字3个字节
, 这里的代码只是一个示例):
package main
import (
"fmt"
"strings"
"io"
)
func main() {
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
)
Closer 接口
接口定义如下:
type Closer interface {
Close() error
}
该接口比较简单, 只有一个Close()
方法, 用于关闭数据流.
文件(os.File)
, 归档(压缩包)
, 数据库连接
, Socket
等需要手动关闭的资源都实现了Closer
接口.
实际编程中, 经常讲Close
方法的调用放在defer
语句中.
提示
初学者容易写出这样的代码:
file, err := os.Open("test.txt")
defer file.Close()
if err != nil {
...
}
当文件test.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
之前校验错误是个好习惯!
其他接口
ByteReader 和 ByteWriter
通过名称大概也能猜出这组接口的用途: 读或者写一个字节. 接口定义如下:
type ByteReader interface {
ReadByte() (c byte, err error)
}
type ByteWriter interface {
WriterByte(c byte) error
}
在标准库中, 有如下类型实现了io.ByteReadre或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
来一次读取或者写入一个字节(主要代码):
package main
import (
"fmt"
"bytes"
)
func main() {
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
读取该字节.
一般地, 我们不会使用bytes.Buffer
来一次读取或者写入一个字节. 那么, 这两个接口有哪些用处呢?
在标准库encoding/binary
中, 实现Google-ProtoBuf中的Varints
读取, ReadVarint就需要一个io.ByteReader
类型的参数, 也就是说, 它需要一个字节一个字节的读取.关于 encoding/binary
包在后面会详细介绍
在标准库image/jpeg
中, Encode
函数的内部实现使用了ByteWritre
写入一个字节
小贴士
可以通过在Go语言源码src/pkg中搜索io.ByteReader
或 io.ByteWriter
,获得哪些地方用到了这两个接口. 你会发现, 这两个接口在二进制数据或归档压缩用的比较多.
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
包有用到.
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
的功能.
由于该类型所支持的操作, 前面都有介绍. 关于该类型在标准库中的使用, 我们在archive/zip - zip 归档访问会讲到.
LimitedReader 类型
LimitedReader
结构定义如下:
type LimitedReader struct {
R Reader // underlying reader, 最终的读取操作通过 R.Read 完成
N int64 // max bytes remaining
}
文档说明如下:
从
R
读取但将返回的数据量限制为N
个字节. 每调用一次Read
都讲更新N
来反应新的剩余数量. 也就是说, 最多只能返回N
字节数据.
LimitedReader
只实现Read
方法(Reader接口)
使用示例如下:
package main
import (
"fmt"
"io"
"strings"
)
func main() {
content := "This is LimitReader Example"
reader := strings.NewReader(content)
limtReader := &io.LimitedReader{R: reader, N: 8}
for limtReader.N > 0 {
tmp := make([]byte, 2)
limtReader.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
接口.
结构定义如下:
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
(即调用CloseWriteError
关闭), 该Write
返回的err
就是读取端传递的error
;否则err
为ErrClosePipe
.
使用示例如下:
package main
import (
"errors"
"fmt"
"io"
"time"
)
func main() {
pipeReader, pipeWriter := io.Pipe()
go PipWrite(pipeWriter)
go PipeRead(pipeReader)
time.Sleep(30 * time.Second)
}
func PipWrite(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
.
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("Go EOF -- bye")
}
执行: echo "Hello, World" | go run zrq.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
接口, 复制操作也就会使用它来实现.
代码:
package main
import (
"io"
"os"
"strings"
)
func main() {
io.CopyN(os.Stdout, strings.NewReader("G语言标准库学习"), 8)
}
会输出:
Go语言
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 函数
这是为了方便写入string
类型提供的函数, 函数签名:
func WriteString(w Writer, s string) (n int, err error)
函数文档;
WriteString
将s
的内容写入w
中, 当w
实现了WriteString
方法时, 会直接调用该方法, 否则执行w.Write([]bytes(s))
MultiReader 和 MultiWriter 函数
这两个函数的定义分别是:
func MultiReader(readers ...Reader) Reader
func MultiWriter(writers ...Writer) Writer
它们接收多个Reader
或者Writer
, 返回一个Reader
或者Writer
. 我们可以猜想到这两个函数就是操作做个Reader
或Writer
就像操作一个.
事实上, 在io
包中定义了两个非导出类型: multiReader
和multiWriter
, 它们分别实现了io.Reader
和io.Writer
接口. 类型定义为:
type multiReader struct {
readers []Reader
}
type multiWriter struct {
writers []Writer
}
对于这两种类型对应的实现方法(Read
和Write
方法) 的使用,我们通过例子来演示.
MultiReader的使用:
package main
import (
"bytes"
"fmt"
"io"
"strings"
)
func main() {
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
的Reader
方法获取到的是slice
中第一个元素的内容… 也就是说, MultiReader
只是逻辑上将多个Reader
组合起来, 并不能通过调用一次Reader
方法获取所有Reader
内容. 在所有的eader
内容都被读完后,Reader
会返回EOF
.
MultiWriter的使用:
package main
import (
"io"
"os"
)
func main() {
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
命令相似.
TeeReader 函数
函数签名如下:
func TeeReader(r Reader, w Writer) Reader
TeeReader
返回一个Reader
, 它将从r
中读到的数据写入w
中.所有经由它处理的从r
的读取都匹配于对应的对w
的写入. 它没有内部缓存, 即写入必须在读取完成前完成. 任何写入时遇到的错误都将作为读取错误返回.
也就是说, 我们通过Reader
读取内容后, 会自动写入到Writer
中去. 例子代码如下:
package main
import (
"io"
"os"
"strings"
)
func main() {
reader := io.TeeReader(strings.NewReader("Go语言标准库学习"), os.Stdout)
reader.Read(make([]byte, 20))
}
输出结果:
Go语言标准库学习
这种功能的实现其实很简单,无非是在Read
完之后执行Write
.
至此, io
所有接口, 类型和函数都讲解完了.
「真诚赞赏,手留余香」
真诚赞赏,手留余香
使用微信扫描二维码完成支付
