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将会导致panic
package 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)
	}
	*/
}
注:CopyBuffer内部会对len(buf) > 0的缓冲区重复调用Read函数知道遇到EOF或错误,所以如果提供的缓冲区过小,会导致Read函数被调用多次,如果buf为nil,则会分配很大的缓冲区进行读操作,当然len(buf) == 0则会直接panic

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 == nil
package 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,则返回err
package 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,客户的调用或实现应该能够并发的在同一个数据源调用ReatAt
type 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方法不执行任何操作的ReadCloser

func ReadAll(r io.Reader) ([]byte, error)

从r中读取数据直到出现错误或io.EOF,成功调用会返回err == nil而不是EOF
package 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,错误将返回err
package 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创建文件并写入,如果文件存在,则在写入之前先截断文件