Io
Io库概述
IO库提供了一些IO操作的原语,主要任务是封装一些存在的实现,比如os库里的,为共享的功能提供抽象的接口,附加一些辅助操作,因为这些操作都是底层的调用,如若无特别声明,都不是并发安全的常量
IO库提供了三个常量,用于文件定位:const (
SeekStart = 0 //定位到文件开始位置
SeekCurrent = 1 //定位到文件当前位置
SeekEnd = 2 //定位到文件结束位置
)
错误处理
IO提供里几个错误变量,可以很方便的处理读写错误EOF错误用于应对处理文件读到结尾的情况,通常是由Read函数返回,提示用户已没有更多数据可读
var EOF = errors.New("EOF")
ErrClosedPipe用于提示在已经关闭的Pipe(管道)进行读写操作的错误
var ErrClosedPipe = errors.New("io:read/write on closed pipe")
ErrNoProgress主要用于标识用户提供的io.Reader实现违反了Read函数的要求,也就是说用户实现的io.Reader接口不符合规格
var ErrNoProgress = errors.New("multiple Read calls return no data or error")
ErrShortBuffer意味着读操作要求的Buffer(缓存区)长度大于用户提供的
var ErrShortBuffer = errors.New("short buffer")
ErrShortWrite意味者实际写入的数据比要求的少,但没能返回明确的错误
var ErrShortWrite = errors.New("short write")
ErrUnexpectedEOF意味着出现了EOF错误,但是没能读到要求的Fix-Size Block或数据结构
var ErrUnexpectedEOF = errors.New("unexpected EOF")
io.Reader && io.Writer
前面说过io库是对os的读写原语进一步抽象,所以让我们先了解一下io库提供的两个接口,只要我们实现了它们,就可以很方便的复用io.Reader
实现建议:读取时,如果有数据提供(即使 0 < n < len(buf)),也要立即返回(不要等到缓冲区读满)
读完数据后,如果n>0,则可以返回n,nil或者n,EOF,但下次调用必须返回0,EOF
不建议返回0,nil除非len(buf) == 0,否则都应该返回0,EOF,表明没数据可读,调用者应该把0,nil看出什么事都没发生,不表明已经EOF
接口定义:
type Reader interface {
Read(buf []byte) (n int, err error)
}
io.Writer
io.Writer是写操作接口的封装,Write函数将缓冲区buf中len(buf)字节写入底层数据流,返回写入的数据字节(0<= n <= len(buf))和造成Write函数过早结束的错误实现建议:
当写入的字节小于len(buf),应该返回non-nil错误
不要修改写入缓冲区,即使是临时缓冲区
接口定义:
type Writer interface {
Write(buf []byte) (n int, err error)
}
相关函数
func Copy(dst Writer, src Reader) (written int64, err error)
复制src到dst直到遇到EOF或者遇到其他错误,返回读取的字节数和复制时出现的错误,如果读取成功的话便会返回err==nil而不是err == EOF,因为内部会调用Read直到遇到EOF或错误,并不将EOF视为错误如果src实现了WriteTo接口,Copy实现将会调用src.WriteTo(dst),否则,如果dst实现了ReadFrom接口,Copy实现将会直接调用dst.ReadFrom(src)
package main
import (
"io"
"strings"
"os"
)
func main() {
r := strings.NewReader("hello, go language")
if _, err := io.Copy(os.Stdout, r); err != nil {
panic(err)
}
// Output: hello, go language
}
func CopyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error)
CopyBuffer和Copy相同,除了CopyBuffer需要提供缓冲区,而不是分配临时缓冲区,如果buf是nil,将会分配一个缓冲区,如果len(buf) == 0将会导致panicpackage main
import (
"io"
"strings"
"os"
"fmt"
)
func main() {
r := strings.NewReader("hello, copybuffer from io library")
buf := make([]byte, 8)
//buf used here
if _, err := io.CopyBuffer(os.Stdout, r, buf); err != nil {
panic(err)
}
// still 8
fmt.Println(len(buf))
r = strings.NewReader("copybuffer i want used once again")
//buf used again, avoid allocate once again
if _, err := io.CopyBuffer(os.Stdout, r, buf); err != nil {
panic(err)
}
// still 8
fmt.Println(len(buf))
// panic if give len(buf) == 0
/*
if _, err := io.CopyBuffer(os.Stdout, r, make([]byte, 0)); err != nil {
panic(err)
}
*/
}
func CopyN(dst Writer, src Reader, n int64) (written int64, err error)
由src向dst复制直到n字节或出现错误,复制n字节成功,当且仅当 err == nil(内部会通过转化为LimitReader调用Copy)package main
import (
"strings"
"io"
"os"
)
func main() {
r := strings.NewReader("hello, copyn from io library")
if _, err := io.CopyN(os.Stdout, r, 5); err != nil {
panic(err)
}
// Output: hello
}
func ReadAtLeast(src Reader, buf []byte, min int) (n int, err error)
读取src到buf至少min字节,如果len(buf) < min将会返回ErrShortBuffer,如果 n < min将会返回ErrUnexpectedEOF,n >= min 当且仅当 err == nil(已经读完 n >= min时Read恰好出现错误的话,错误将会内部被丢弃)package main
import (
"io"
"strings"
"fmt"
)
func main() {
r := strings.NewReader("hello, readatleast from io library")
buf := make([]byte, 30)
if _, err := io.ReadAtLeast(r, buf, 20); err != nil {
panic(err)
}
fmt.Println(string(buf))
if _, err := io.ReadAtLeast(r, buf, 60); err != nil {
fmt.Println(err)
}
if _, err := io.ReadAtLeast(r, buf, 5); err != nil {
fmt.Println(err)
}
// Output:
// hello, readatleast from io lib
// short buffer
// unexpected EOF
}
func ReadFull(r Reader, buf []byte) (n int, err error)
精确地读取len(buf)字节,如果没有填充完buf就遇到EOF,将返回ErrUnexpectedEOF, n == len(buf)当且仅当 err == nilpackage main
import (
"io"
"strings"
"fmt"
)
func main() {
r := strings.NewReader("hello, readfull from io library")
buf := make([]byte, 5)
if _, err := io.ReadFull(r, buf); err != nil {
panic(err)
}
fmt.Println(string(buf))
// Output: hello
}
func WriteString(w Writer, s string) (n int, err error)
将s的内容写入w,如果w实现了StringWriter,将会直接调用w.WriteString(s),否则将会调用w.Write一次package main
import (
"io"
"os"
)
func main() {
text := "hello, WriteString from io library"
if _, err := io.WriteString(os.Stdout, text); err != nil {
panic(err)
}
// Output: hello, WriteString from io library
}
More Reader && Writer
通过对Reader和Writer进行限制和组合,能够得到更多种类的Reader和Writer,这也比较符合go组合编程的思想func LimitReader(r Reader, n int) Reader
将r转化为读n字节后就返回EOF的Reader(底层具体类型是*LimitedReader)package main
import (
"io"
"strings"
"os"
)
func main() {
r := strings.NewReader("hello, LimitReader from io library")
lr := io.LimitReader(r, 5)
if _, err := io.Copy(os.Stdout, lr); err != nil {
panic(err)
}
// Output: hello
}
func MultiReader(readers ...Reader) Reader
串联所有的Reader,返回的Reader调用Read读时会遍历所有Reader进行读操作知道遇到EOF或错误,如若遇到err != nil && err != EOF,则返回errpackage main
import (
"os"
"io"
"strings"
)
func main() {
r1 := strings.NewReader("I'm first reader\n")
r2 := strings.NewReader("I'm second reader\n")
r3 := strings.NewReader("I'm third reader\n")
r := io.MultiReader(r1, r2, r3)
if _, err := io.Copy(os.Stdout, r); err != nil {
panic(err)
}
// Output:
/*
I'm first reader
I'm second reader
I'm third reader
*/
}
func TeeReader(r Reader, w Writer) Reader
将一个Reader和Writer关联返回一个新的Reader,当读时会在内部将读到数据写入Writer,然后返回数据,数据会在w里,所以没有内部缓冲区,所有内部写操作的错误会被报告成外部读错误package main
import (
"io"
"strings"
"bytes"
"io/ioutil"
"fmt"
)
func main() {
r := strings.NewReader("some data read from here")
buf := new(bytes.Buffer)
teeReader := io.TeeReader(r, buf)
printbuf := func (r io.Reader) {
data, err := ioutil.ReadAll(r)
if err != nil {
panic(err)
}
fmt.Println(string(data))
}
printbuf(teeReader)
printbuf(buf)
// Output:
/*
some data read from here
some data read from here
*/
}
func MultiWriter(writers ...Writer) Writer
与MultiReader类似,MultiWriter返回一个将writers串联的Writer,写操作时会遍历writer对所有Writer进行Write操作,只要其中一个Writer出现错误,将会停止遍历,并返回错误package main
import (
"io"
"bytes"
"strings"
"os"
"fmt"
)
func main() {
r := strings.NewReader("some data will be read\n")
buf := new(bytes.Buffer)
w := io.MultiWriter(os.Stdout, buf)
if _, err := io.Copy(w, r); err != nil {
panic(err)
}
fmt.Print(buf.String())
// Output:
/*
some data will be read
some data will be read
*/
}
More Abstract
除了Reader,Writer,io库还提供了更多操作的抽象,不过它们还是和Reader和Writer有关ReadWriter
Reader和Writer接口的组合type ReadWriter interface {
Reader
Writer
}
Closer
Closer接口主要是用来组合那些需要在读写正确关闭操作的接口,使得资源能够被正确释放,Close调用后再调用是未定义的,特别的实现需要文档说明type Closer interface {
Close() error
}
ReadCloser
主要用来组合基本的Read和Close操作type ReadCloser interface {
Reader
Closer
}
WriteCloser
主要用来组合基本的Write和Close操作type WriteCloser interface {
Writer
Closer
}
ReadWriteCloser
Reader,Writer,Closer接口的组合type ReadWriteCloser interface {
Reader
Writer
Closer
}
Seeker
whence参数,SeekStart会调整相对文件开头的偏移量,SeekCurrent会定位到相对当前位置的偏移量,SeekEnd会定位到相对文件结束位置的偏移量,定位到文件开头之前会发生错误,任何offset都是合法的,但具体操作依赖于底层实现type Seeker interface {
Seek(offset int64, whence int) (int64, error)
}
ReadSeeker
Reader和Seeker接口的组合type ReadSeeker interface {
Reader
Seeker
}
WriteSeeker
Writer和Seeker接口的组合type WriteSeeker interface {
Writer
Seeker
}
ReadWriteSeeker
Reader,Writer,Seeker接口的组合type ReadWriteSeeker interface {
Reader
Writer
Seeker
}
ReadAt
ReadAt是底层ReadAt方法的封装,ReadAt会读取底层输入源的偏移位置len(p)字节数据,如果读取的数据少于len(p),方法会返回错误报告原因(这点要比Read方法严格),如果有数据可供读取,而读取的数据没有len(p),将会阻塞读到len(p)或直到出现错误(这点和Read函数不同),如果读取到len(p) == n,则err == nil或 err == EOF,ReadAt实现不应该影响或不被影响底层数据源的seek offset,客户的调用或实现应该能够并发的在同一个数据源调用ReatAttype ReadAt interface {
ReadAt(p []byte, off int64) (n int, err error)
}
ReadFrom
基础方法ReadFrom的封装接口,对r调用Read直到出现EOF或其他错误,返回读到的数据和错误(不将EOF视为错误,遇到EOF会返回nil)type ReadFrom interface {
ReadFrom(r Reader) (n int64, err error)
}
WriteAt
WriteAt是底层WriteAt方法封装的接口,WriteAt会往底层数据流其偏移off处写入len(p)字节数据,如果写入数据小于len(p)将会返回non-nil错误,只要写入出现错误就会停止写入并立即返回,客户可以并发的调用写往同一个数据流,只要其范围不重叠type WriteAt interface {
WriteAt(p []byte, off int64) (n int, err error)
}
WriteTo方法的封装接口,WtiteTo进行写操作直到没有数据可写,当写完数据或发生错误时会返回,返回写入的数据和写入时遇到的错误
type WriteTo interface {
WriteTo(w Writer) (n int64, err error)
}
More Combination
通过对读写的数据的分类,读写来源的不同,我们能得到更多的组合ByteReader
ByteReader是ReadByte方法的封装接口,通过调用该方法可以获得输入源下一个字节或者遇到的错误,发生错误时,返回的字节是未定义的type ByteReader interface {
ReadByte() (byte, error)
}
ByteScanner
ByteScanner在ByteReader接口增加了UnreadByte方法,UnreadByte用于下次调用ReadBye会返回相同的字节,同时如果调用UnreadByte两次而没有调用ReadByte可能会发生错误type ByteScanner interface {
ByteReader
UnreadByte() error
}
ByteWriter
WriteByte方法的封装接口type ByteWriter interface {
WriteByte(c byte) error
}
RuneReader
RuneReader是ReadRune方法的封装接口,ReadRune会读取一个单UTF-8编码的Unicode字符,并返回Rune值和字节大小,如果字符不可用,将会返回错误type RuneReader interface {
ReadRune() (r rune, size int, err error)
}
RuneScanner
RuneScanner是RuneReader接口和UnreadRune方法的组合,UnreadRune用于下次调用ReadRune和上次调用返回同样的结果,如果调用两次UnreadRune而没调用ReadRune可能会发生错误type RuneScanner interface {
RuneReader
UnreadRune() error
}
StringWriter
StringWriter是封装WriteString方法的接口type StringWriter interface {
WriteString(s string) (n int, err error)
}
Pipe(管道)
管道是io库提供的一种基于内存的同步IO设施,它用于在期望读写的两端构建联系,这两端分别是PipeReader和PipeWriter,每次PipeWriter写操作都会阻塞到数据全部被读取(消费),所以它是一种即时的操作,内部不会缓存数据,同时它的操作也是并发安全的,可以通过下面函数创建管道:func Pipe() (PipeReader, PipeWriter)
对于PipeReader有关于读,关闭的方法func (r *PipeReader) Read(data []byte) (n int, err error)
Read函数实现了标准的Reader接口,Read函数会在Write数据到来之前一直阻塞或写端已经关闭时返回,如果写端关闭给出错误信息,将会返回err,否则返回EOF
func (r *PipeReader) Close() error
关闭读端,接下来的的写入端写入将会返回ErrClosedPipe
func (r *PipeReader) CloseWithError(err error) error
关闭读端,同时给出错误信息,接下来的写入端写入将会返回给出的err(主要用于用户定制化错误信息让写入端进行定制处理)
对于PipeWriter也提供了写和关闭的方法:
func (w *PipeWriter) Write(data []byte) (n int, err error)
写入数据,直到data的数据全部被读取或读端关闭,如果读端关闭时给出错误信息,则返回错误信息,否则返回ErrClosedPipe
func (w *PipeWriter) Close() error
关闭写端,接下来读端读取数据时会返回EOF
func (w *PipeWriter) CloseWithError(err error) error
关闭写端,同时给出错误信息,接下来读端读取数据将会返回错误信息,如果err == nil将会返回EOF
CloseWithError始终返回nil
Example
package main
import (
"io"
"errors"
"bytes"
"fmt"
)
func main() {
r, w := io.Pipe()
CustomErrEOF := errors.New("CustomErrEOF")
go func() {
text := "i have some data want to be read ..."
_, err := w.Write([]byte(text))
if err != nil {
panic(err)
}
w.CloseWithError(CustomErrEOF)
}()
for {
buf := new(bytes.Buffer)
_, err := buf.ReadFrom(r)
if err != CustomErrEOF {
panic(err)
} else {
fmt.Println(buf.String())
break
}
}
}
SectionReader
SectionReader实现了Seek,ReadAt,ReadAt底层ReadAt的一部分,它提供了如下函数:func NewSectionReader(r ReadAt, off int64, n int64) *SectionReader
NewSectionReader返回的*SectionReader是读操作从r偏移off开始读取n个byte以EOF停止func (s *SectionReader) Read(p []byte) (n int, err error)
实现Reader接口的读取函数func (s *SectionReader) ReadAt(p []byte, off int64) (n int, err error)
实现的ReadAt接口的ReadAt函数func (s *SectionReader) Seek(offset int64, whence int) (int64, error)
实现的Seek接口的Seek函数func (s *SectionReader) Size() int64
返回section的字节大小Example
package main
import (
"io"
"strings"
"os"
"fmt"
)
func main() {
r := strings.NewReader("some io.Reader stream to be read\n")
s := io.NewSectionReader(r, 5, 9)
if _, err := io.Copy(os.Stdout, s); err != nil {
fmt.Println(err)
return
}
fmt.Println()
s.Seek(3, io.SeekStart)
if _, err := io.Copy(os.Stdout, s); err != nil {
fmt.Println(err)
return
}
// Output:
/*
io.Reader
Reader
*/
}
io/ioutil
接下来顺便介绍一下io/ioutil库,里面提供了对于使用io库更高效实用的功能变量(Variables)
Discard是一个io.Writer接口变量用于实现将数据丢弃并成功返回var Discard io.Writer = devNull(0)
函数(Functions)
func NopCloser(r io.Reader) io.ReadCloser
将Reader接口r封装成一个Close方法不执行任何操作的ReadCloserfunc ReadAll(r io.Reader) ([]byte, error)
从r中读取数据直到出现错误或io.EOF,成功调用会返回err == nil而不是EOFpackage main
import (
"fmt"
"strings"
"io/ioutil"
)
func main() {
r := strings.NewReader("some data will be to read by ioutil.ReadAll")
all, err := ioutil.ReadAll(r)
if err != nil {
panic(err)
}
fmt.Println(string(all))
}
func ReadDir(dirname string) ([]os.FileInfo, error)
读取dirname目录下的所有文件(包括目录),并以文件名排序成列表返回package main
import (
"io/ioutil"
"fmt"
)
func main() {
files, err := ioutil.ReadDir("/bin")
if err != nil {
panic(err)
}
for _, file := range files {
fmt.Println(file.Name())
}
}
func ReadFile(filename string) ([]byte, error)
读取filename文件的所有数据,成功调用将EOF转换为nil返回nil,错误将返回errpackage main
import (
"io/ioutil"
"fmt"
)
func main() {
filename := "readfile.go"
content, err := ioutil.ReadFile(filename)
if err != nil {
panic(err)
}
fmt.Println(string(content))
}
func TempDir(dir, prefix string) (name string, err error)
在目录名dir下创建以prefix为前缀的临时目录,并返回目录名和错误报告,调用者有责任在不需要时自行清理该临时目录(如果dir是空字符串,程序将会使用默认临时目录)package main
import (
"os"
"io/ioutil"
"fmt"
)
func main() {
tempDir, err := ioutil.TempDir(".", "marcoepsilon")
if err != nil {
panic(err)
}
fmt.Println(tempDir)
defer os.Remove(tempDir)
}
func TempFile(dir, pattern string) (f *os.File, err error)
在目录名dir下创建一个临时文件,如果pattern中存在"*",则程序会以随机字符串代替最后的"*",如果目录dir为空字符串,将在默认临时目录创建,返回一个以读写模式打开的文件句柄package main
import (
"io/ioutil"
"fmt"
"os"
)
func main() {
pattern := "marcoepsilon*.txt"
file, err := ioutil.TempFile(".", pattern)
if err != nil {
panic(err)
}
fmt.Println(file.Name())
defer os.Remove(file.Name())
}
func WriteFile(filename string, p []byte, perm os.FileMode) error
package main
import (
"io/ioutil"
"fmt"
"os"
)
func main() {
text := "some data will be write by ioutil.WriteFile"
filename := "./tempuse.txt"
err := ioutil.WriteFile(filename, []byte(text), os.ModePerm)
if err != nil {
panic(err)
}
defer os.Remove(filename)
content, err := ioutil.ReadFile(filename)
if err != nil {
panic(err)
}
fmt.Println(string(content))
}
向filename文件写入字节序列p,如果文件不存在,则会以perm创建文件并写入,如果文件存在,则在写入之前先截断文件