一、io
1、两个接口
Go 的 io
包提供了 io.Reader
和 io.Writer
接口,分别用于数据的输入和输出;
Go 官方提供了一些 API
,支持对内存结构,文件,网络连接等资源进行操作;
围绕io.Reader/Writer
,有几个常用的实现:
net.Conn
,os.Stdin
,os.File
: 网络、标准输入输出、文件的流读取strings.Reader
: 把字符串抽象成Reader(类似流那样,这样就有各种API了)bytes.Reader
: 把[]byte抽象成Readerbytes.Buffer
: 把[]byte抽象成Reader和Writerbufio.Reader/Writer
: 抽象成带缓冲的流读取(比如按行读写)
什么是流?流是一种数据的载体,通过它可以实现数据交换和传输; 比如说程序读取文件,程序通过流从文件中读取数据,也通过向流中写入数据,最终写入到文件中; 代码中打开文件,就会返回一个流,这个流连通着文件和程序;
关于io.EOF
EOF是End-Of-File的缩写,表示输入流的结尾. 因为每个文件都有一个结尾;
2、io.Reader
该接口只有一个方法,Read(p []byte)
。只要实现了 Read(p []byte)
,那它就是一个读取器。
type Reader interface {
//n是读取到的字节数,err是读取时发生的错误;
//如果资源内容已全部读取完毕,应该返回 io.EOF 错误
Read(p []byte) (n int, err error)
}
//实际使用
p := make([]byte, 4)
for {//循环读取,每次读取4个字节
n, err := reader.Read(p)
if err != nil{
if err == io.EOF {//读取到空时,退出循环
break
}else{
os.Exit(1)
}
}
fmt.Println(n, string(p[:n]))
}
//最后一次返回的 n 值有可能小于缓冲区大小;
复制代码
3、io.Writer
该接口只有一个方法,Write(p []byte)
。只要实现了 Write(p []byte)
,那它就是一个编写器。
type Writer interface {
Write(p []byte) (n int, err error)
}
复制代码
4、辅助函数
//方便地将字符串类型写入一个Writer,本质是对Write([]byte(s))的封装
func WriteString(w Writer, s string) (n int, err error)
//ReadFull从r精确地读取len(buf)字节数据填充进buf。
func ReadFull(r Reader, buf []byte) (n int, err error)
//将src的数据拷贝到dst,直到在src上到达EOF或发生错误。
//io.Copy()可以轻松地将数据从一个 Reader 拷贝到另一个 Writer。
//它抽象出for循环读取的模式并正确处理io.EOF和 字节计数。这样就不用自己读一点写一点了;
func Copy(dst Writer, src Reader) (written int64, err error)
复制代码
5、PipeReader和PipeWriter
Pipe创建一个同步的内存中的管道,类型 io.PipeWriter
和 io.PipeReader
在内存管道中模拟 io 操作。
数据被写入管道的一端,并使用单独的 goroutine 在管道的另一端读取。
read:=strings.NewReader("hello world")
piper, pipew := io.Pipe()
// 将 read 写入 pipew 这一端
go func() {
defer pipew.Close()
io.Copy(pipew, read) //往通道写入数据,在数据被读走之前,该协程将是阻塞的
}()
io.Copy(os.Stdout, piper) //从通道中读取数据
piper.Close()
//源码剖析
func Pipe() (*PipeReader, *PipeWriter) {
p := &pipe{
wrCh: make(chan []byte), //这里是一个同步通道
rdCh: make(chan int),
done: make(chan struct{}),
}
return &PipeReader{p}, &PipeWriter{p}
}
复制代码
6、特殊Reader
func LimitReader(r Reader, n int64) Reader
//返回一个Reader,它从r中读取n个字节后以EOF停止。返回值接口的底层为*LimitedReader类型。
func NewSectionReader(r ReaderAt, off int64, n int64) *SectionReader
//返回一个从r中的偏移量off处为起始,读取n个字节后以EOF停止的SectionReader。
func MultiReader(readers ...Reader) Reader
//返回一个将多个Reader在逻辑上串联起来的Reader接口。
//他们依次被读取。当所有的输入流都读取完毕,Read才会返回EOF。
复制代码
二、io_ioutil
1、ioutil
io
包下面的一个子包 ioutil
封装了一些非常方便的功能
func ReadFile(filename string) ([]byte, error)
//ReadFile 从filename指定的文件中读取数据并返回文件的内容(读取整个文件,考虑内存大小)
func WriteFile(filename string, data []byte, perm os.FileMode) error
//函数向filename指定的文件中写入数据。如果文件不存在将按给出的权限创建文件,否则在写入数据之前清空文件。
复制代码
2、函数
//将Reader中的数据一次性读取出来
func ReadAll(r io.Reader) ([]byte, error)
//将文件中的数据一次性读取出来
func ReadFile(filename string) ([]byte, error)
//将文件中写入数据,如果文件不存在将按给出的权限创建文件
func WriteFile(filename string, data []byte, perm os.FileMode) error
复制代码
3、os.File
类型 os.File
表示本地系统上的文件。它实现了 io.Reader
和 io.Writer
,因此可以在任何 io 上下文中使用。
4、os.Stdout
,os.Stdin
和 os.Stderr
这三者类型均为 *os.File
,分别代表 系统标准输入
,系统标准输出
和 系统标准错误
的文件句柄。
os.Stdout.Write([]byte("hello world")) //直接输出到终端
os.Stderr.Write([]byte("this is an error")) //直接输出到终端
p := make([]byte, 10) //获取终端输入
reader := bufio.NewReader(os.Stdin)
_, err := reader.Read(p)
复制代码
三、bufio
bufio 包实现了缓存IO。它包装了 io.Reader 和 io.Writer 对象,创建了另外的Reader和Writer对象,它们也实现了io.Reader和io.Writer接口,不过它们是有缓存的。 数据缓冲功能,能够一定程度减少大块数据读写带来的开销。
简单说就是,把文件读取进缓冲(内存)之后再读取的时候就可以避免文件系统的io 从而提高速度。
1、Reader 类型和方法
type Reader struct {
buf []byte // 缓存
rd io.Reader // 底层的io.Reader
r, w int
err error // 读过程中遇到的错误
lastByte int // 最后一次读到的字节
lastRuneSize int // 最后一次读到的Rune的大小
}
//将 rd 封装成一个带缓存的 bufio.Reader 对象,缓存大小由 size 指定
//如果rd已经带用足够的缓存,则将rb转化为*Reader类型,直接返回
func NewReaderSize(rd io.Reader, size int) *Reader
//默认缓存大小,defaultBufSize=4096
func NewReader(rd io.Reader) *Reader {
return NewReaderSize(rd, defaultBufSize)
}
//Peek 返回缓存的一个切片,该切片引用缓存中前 n 个字节的数据,
//该操作不会将数据读出,只是引用.
func (b *Reader) Peek(n int) ([]byte, error)
复制代码
话说read的时候,还要我自定义p []byte,这个包有什么鸟用?
底层的缓冲区有什么鸟用?
p:=make([]byte,1024)
buff.Read(p)
底层的缓冲是为了避免多次访问磁盘,提高性能;
上层的[]byte是。。。
//源码剖析
func (b *Reader) Read(p []byte) (n int, err error) {}
1、Read从b中读取数据到p中,如果缓存不为空,则只能读取缓存中的数据,不会从底层io.Reader中提取数据
2、如果缓存为空:
(1)len(p) >= 缓存大小,则跳过缓存,直接从底层io.Reader中提取数到p
(2)len(p) < 缓存大小,则先将数据从io.Reader读取到缓存中,再从缓存copy到p中;(内存之间直接赋值是很快的)
缓存中被读取的部分都将被清空;
复制代码
2、Writer
func (b *Writer) Write(p []byte) (nn int, err error) // 写入n byte数据
func (b *Writer) Reset(w io.Writer) // 重置当前缓冲区
func (b *Writer) Flush() error // 清空当前缓冲区,将数据写入输出
复制代码
3、Scanner
Scanner是有缓存的,意思是Scanner底层维护了一个Slice用来保存已经从Reader中读取的数据; Scanner本身不负责关闭文件描述符,需要自己在外面关闭;
这缓冲区是怎么样的?
file,err:=os.Open("./file.txt")
//和Reader类似,Scanner需要绑定到某个io.Reader上
scan:=bufio.NewScanner(file)
//设置缓冲区大小,默认是MaxScanTokenSize = 64 * 1024
//这里缓冲区大小设置为2,不够时会拓展为原来的2倍,最大为1024
buf:=make([]byte,2)
scan.Buffer(buf,1024)//要在scan之前调用,否则panic
//设置分割方式,要在scan之前调用,否则panic
//scan.Split(bufio.ScanWords)//输出单个单词,也就是按空格分割
//scan.Split(bufio.ScanLines)//一行一行输出,也就是按行分割(这是默认的方式)
//scan.Split(bufio.ScanRunes)//一个字符一个字符输出
//scan.Split(bufio.ScanBytes)//一个字节一个字节输出
//自定义分割方式
onComma := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
for i := 0; i < len(data); i++ {
if data[i] == ';' {//自定义分割符号(分割符号是不会被读取出来的)
return i + 1, data[:i], nil
}
}
if !atEOF {
return 0, nil, nil
}
return 0, data, bufio.ErrFinalToken
}
scan.Split(onComma)
//Scan方法获取当前位置的token,并让Scanner的扫描位置移动到下一个token。
//返回false时,表示EOF或者读取时产生错误
//这里的token是指分隔符与分隔符之间的这段字符
for scan.Scan(){
fmt.Println(scan.Text())//返回最近一次Scan调用生成的token
}
//Scan返回false了,Err看一下是EOF还是其他错误;
//在Scan方法返回false后,Err方法将返回扫描时遇到的任何错误;除非是io.EOF,此时Err会返回nil。
if err=scan.Err();err!=nil{
fmt.Println(err)
}
复制代码
四、bytes/strings
1、这两个包是相当类似的,只是一个操作[]byte,一个操作string罢了
2、功能:equal,container,ToLower,ToUpper,repeat(返回n个串联的字符串),replace,Trim,Split,Join(连接多个string)
3、bytes还拥有buff功能
//简单使用
buf := make([]byte, 1024) //定义缓冲区大小
b := bytes.NewBuffer(buf)
file,err:=os.Open("./file.txt")
b.ReadFrom(file)//读取数据到缓冲区
b.WriteTo(os.Stdout)//将缓冲区输出到io.Writer
b.String() //字符串化
b.Reset() //清空数据
复制代码
有疑问加站长微信联系(非本文作者)