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 当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 同样的,所有实现了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 即使ReadAt返回的n 若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 若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