Golang io标准库
1、常用接口
io包为I/O提供了原语的基本接口。他的主要工作是将这些原语的现有实现
在io包中最重要的两个接口:ReaderWriter接口。只要满足这个两个接口,他就是可以使用IO包的功能。
1.1Reader接口
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)。
调用者在考虑错误之前应当首先处理返回的数据。这样做可以正确地处理在读取一些字节后产生的I/O错误,同时允许EOF的出现。
根据Go语言中关于接口和满足了接口的类型和定义,Reader接口方法集只包含一个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]
}
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)
io.EOF变量的定义:var EOF= errors.New("EOF"),是error类型。根据reader接口的说明,在n>0且数据被读完了的情况下,当返回的error有可能是EOF也有可能是nil.
1.2 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.Writer类型的参数(第一个参数),也就是说他们将数据格式化输出到io.Writer中。那么,调用这组函数时,该如何传递这个参数呢?
以fmt.Fprintln为例,同时看下fmt.Println函数源码。
func Println(a ...interface{}) (n int, err error) {
return Fpringln(os.Stdout,a...)
}
很显然,fmt.Println会将内容输出到标准输出中。
实现了io.Reader接口或io.Write接口的类型
标准库中有哪些类型实现了io.Reader或io.Writer接口?
通过上面的示例,可以知道,os.File同时实现了这两个接口。还可以看到os.Stdin/os.Stdout这样的代码,他们似乎分别实现了io.Reader/io.Writer接口。
实际上在os包中有这样的代码:
var (
Stdin = NewFile(uinptr(syscall.Stdin),"/dev/stdin")
Stdout = NewFile(uinptr(syscall.Stdout),"/dev/stdout")
Stderr = NewFile(uinptr(syscall.Stderr),"/dev/stderr")
)
也就是说,Stdin/Stdout/Stderr只是三个特殊的文件类型的标识(都是os.file的实例),自然也实现了io.Reader和io.Writer。
目前,Go文档中还没有直接列出实现了某个接口的所有类型。不过,可以通过查看标准库文档,列出实现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
-
crypro/tls.Conn同时实现了io.Reader和io.Writer
-
encoding/csvReader/Writer分别实现了io.Reader和io.Writer
-
mime/multipart。Part实现了io.Reader
-
net/conn分别实现了io.Reader和io.Writer(Conn接口定义了Read/Write)
除此之外,io本身也有这两个接口的实现类型:
实现了Reader的类型:LimitReader、PipeReader、SectionReader
实现了Writer的类型:PipeWriter
以上类型中,常用的类型有:os.File、strings.Reader、bufio.Reader/Writer、bytes.Buffer、bytes.Reader
1.3 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)
output>>>
中国,6
WriteAt接口定义:
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调用。
可以通过该接口将数据写入到数据流的特定偏移量之后。
示例:
file,err := os.Create("writeAt.txt")
if err != nil {
panic(err)
}
defer file.Close()
file.WriteString("芝麻开花--补充")
n,err := file.WriteAt([]byte("节节高"),14)
if err != nil {
panic(err)
}
fmt.Println(n)
writeAt.txt内容:芝麻开花--节节高
1.4 ReaderFrom和WriterTo接口
ReaderFrom定义:
type ReaderFrom interface {
ReadFrom(r Reader) (n int64,err error)
}
官方文档说明:
ReadFrom从r中读取数据,知道EOF或发生错误。其返回值n为读取的字节数。除io.EOF之外,在读取过程中遇到的任何错误也讲被返回。
注意: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接口,有两种思路:
1、先获取文件的大小(File的Stat方法),之后定义一个该大小的[]byte,通过Read一次性读取。
2、定义一个小的[]byte,不断的调用Read方法直到遇到EOF,将所有读取到的[]byte连接到一起。
查看bufio.Writer或strings.Buffer类型的ReadFrom方法实现,会发现,其实它们的实现和上述第二种思路类似。
WriteTo定义:
type WriteTo interface {
WriteTo(w Writer) (n int64,err error)
}
官方文档说明:
WriteTo将数据写入w中,直到没有数据可写或发生错误。其返回值n为写入的字节数,在写入过程中遇到的而任何错误也将返回。
如果WriteTo可用,Copy函数就会使用它
其中ReadFrom和WriteTo接口的方法接收的参数是io.Reader和io.Writer类型。根据io.Reader和io.Writer接口的讲解,对该接口的使用应该可以很好的掌握。
示例:
reader := bytes.NewReader([]byte("中国北京"))
reader.WriteTo(os.Stdout)
通过io.ReaderFrom和io.WriterTo的学习,这样的需求可以使用这个两个接口:“一次性从某个地方读或写到某个地方去”
1.5 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("北京欢迎你")
reader.Seek(-3,2)
r,_,_ := reader.ReadRune()
fmt.Printf("%c\n",r)
whence的值,在io包中定义了相应的常量
// Seek whence values.
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.6 Close接口
接口定义:
type Closer interface {
Close() error
}
Close()方法用于关闭数据流
文件(os.File)、归档(压缩包)、数据库连接、Socket等需要手动关闭的资源都实现了Closer接口。
实际中,经常将Close()方法的调用放在defer语句中。
2、其他接口
2.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
示例:
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来一次读取或写入一个字节。那么,这两个接口有哪些用处呢?
在标准库encode/binary中,实现google-ProtoBuf中的Varints读取,ReadVarint就需要一个io.ByteReader类型的参数,也就是说,它需要一个字节一个字节的读取。
在标准库image/jpeg中,Encode函数内部实现使用了ByteWriter写入一个字节。
在Go源码src/pkg中搜索io.ByteReader或io.ByteWriter,这两个接口在二进制数据或归档压缩时用的比较多。
2.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 successful read
RuneReader接口和ByteReader类似,只是ReadRune方法读取单个UTF-8字符,返回其rune和该字符占用的字节数。该接口在regexp包有用到。
问题:
strings.Index("行业交流群","交流")返回的单字节字符的位置:6。但是想要的是unicode字符的位置2。
借助utf8的RuneCountlnString函数,实现代码:
//strings.Index的UTF-8版本
//即Utf8Index("行业交流群","交流")返回的是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])
}
2.3 ReadCloser、ReadSeeker、ReadWriteCloser、ReadWriteSeeker、ReadWriter、WriteCloser、WriteSeeker
这些接口是上面介绍的接口的两个或三个组合而成的新接口。
例如:
type ReadWriter interface {
Reader
Writer
}
这是Reader接口和Writer接口的简单组合(内嵌)
这些接口的作用是:有些时候同时需要某两个接口的所有功能,即必须同时实现了某两个接口的类型才能够被传入使用。可见,io包中有大量的“小接口”,这样方便组合为“大接口”。
2.4 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的功能。
该类型在标准库zip归档访问会涉及
2.5 LimitedReader类型
结构定义:
type LimitedReader struct {
R Reader //underlying reader 最终的读取操作通过R.Read完成
N int64 //max bytes reaining
}
官方文档说明:
从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)
}
output>>>
This Is
通过该类型可以达到只允许读取一定长度数据的目的。
在io包中,LimitReader函数的实现其实就是调用LimitedReader:
func LimitReader(r Reader,n int64) Reader{ return &LimitedReader{r,n} }
2.6 PipeReader和PipeWriter类型
PipeReader(一个没有任何导出字段的struct)是管道的读取端。它实现了io.Reader和io.Closer接口。
结构定义:
type PipeReader struct {
p *pipe
}
关于PipeReader.Read方法的说明:从管道中读取数据。该方法会堵塞,直到管道写入端开始写入数据或写入端被关闭。如果写入端关闭时带有error(即调用CloseWithError关闭),该Read返回的err就是写入端传递的error;否则err为EOF。
PipeWither(一个没有任何导出字段的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("学无止境")
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("接口端开始阻塞5s...")
time.Sleep(time.Second * 5)
fmt.Println("接收端开始接受")
n, err := reader.Read(buf)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("收到字节:%d\nbuf内容:%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。
2.7 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("学无止境"))
output>>>
学无止境
也可以这样:
package main
import (
"fmt"
"io"
"os"
)
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个字节(或到一个error)从src复制到dst。它返回赋值的字节数以及在复制时遇到的最早的报错。当 err==nil时,written==n。
若dst实现了ReaderFrom接口,复制操作也就会使用它来实现。
示例:
io.CopyN(os.Stdout,strings.NewReader("学无止境"),6)
output>>>
学无
2.8 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个字节。
2.9 WriteString函数
这是为了方便写入string类型提供的函数
函数签名:
func WriteString(w Write,s string) (n int,err error)
官方文档说明:
WriteString将s的内容写入w中,当w实现了WriteString方法时,会直接调用该方法,否则执行w.Write([]byte(s))只会被调用一次。
3.0 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的使用:
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)
}
fmt.Println(n)
fmt.Println(string(buf))
data = append(data, buf[:n]...)
}
fmt.Printf("%s\n", data)
output>>>
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("multiWriter.txt")
if err != nil {
panic(err)
}
defer file.Close()
writers := []io.Writer{
file,
os.Stdout,
}
writer := io.MultiWriter(writers...)
writer.Write([]byte("学无止境"))
执行后生成multiWriter.txt文件,同时在文件和屏幕中都输出“学无止境”。与unix的tee命令类似
3.1 TeeReader函数
函数签名:
func TeeReader(r Reader,w Writer) Reader
官方文档说明:
TeeReader返回一个Reader,它将从r中读到的数据写入w中。所有经由它处理的从r的读取都匹配于对应的对w的写入。它没有内部缓存,即写入必须在读取完成前完成。任何写入时遇到的错误都讲作为读取错误返回。
也就是说,通过Reader读取内容后,会自动写入到Writer中去。
示例:
reader := io.TeeReader(strings.NewReader("学无止境"),os.Stdout)
reader.Read(make([]byte,12))
output>>>
学无止境
这功能的实现简单,无非是在Read完后执行Write