Golang | IO库

_Liu_ · · 920 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

一、io

1、两个接口

Go 的 io 包提供了 io.Readerio.Writer 接口,分别用于数据的输入和输出;

Go 官方提供了一些 API,支持对内存结构文件网络连接等资源进行操作;

围绕io.Reader/Writer,有几个常用的实现:

  • net.Conn, os.Stdin, os.File: 网络、标准输入输出、文件的流读取
  • strings.Reader: 把字符串抽象成Reader(类似流那样,这样就有各种API了)
  • bytes.Reader: 把[]byte抽象成Reader
  • bytes.Buffer: 把[]byte抽象成Reader和Writer
  • bufio.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)

//ReadFullr精确地读取len(buf)字节数据填充进buffunc 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.PipeWriterio.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停止的SectionReaderfunc MultiReader(readers ...Reader) Reader
//返回一个将多个Reader在逻辑上串联起来的Reader接口。
//他们依次被读取。当所有的输入流都读取完毕,Read才会返回EOF复制代码

二、io_ioutil

1、ioutil

io 包下面的一个子包 ioutil 封装了一些非常方便的功能

func ReadFile(filename string) ([]byte, error)
//ReadFilefilename指定的文件中读取数据并返回文件的内容(读取整个文件,考虑内存大小)

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.Readerio.Writer ,因此可以在任何 io 上下文中使用。

4、os.Stdoutos.Stdinos.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、如果缓存为空:
(1len(p) >= 缓存大小,则跳过缓存,直接从底层io.Reader中提取数到p
(2len(p) < 缓存大小,则先将数据从io.Reader读取到缓存中,再从缓存copy到p中;(内存之间直接赋值是很快的)
缓存中被读取的部分都将被清空;
复制代码

1570714023755

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()	//清空数据
复制代码

有疑问加站长微信联系(非本文作者)

本文来自:掘金

感谢作者:_Liu_

查看原文:Golang | IO库

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

920 次点击  
加入收藏 微博
下一篇:Golang | json
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传