Go io

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

Golang中输入和输出操作使用原语实现,这些原语将数据模拟称为可读或可写的字节流。为此,Golang的io包提供了io.Readerio.Writer接口,分别用于数据的输入和输出。

io

Golang的io包提供了对I/O原语的基本接口,包装了原语的已有实现,使之成为共享的公共接口,抽象出泛用的函数并附加了相关的原语操作。这些接口和原语是对底层实现完全不同的低水平操作的包装,除非得到其它方面的通知,客户端不应该假设它们是并发执行安全的。

io.EOF

  • EOF是End-Of-File的缩写,根据Go惯例大写字母缩写表示常量,不过io.EOF被定义成了变量。
  • io.EOFio包中的一个变量,表示文件结束的错误。
1package io23var EOF = errors.New("EOF")
  • io.EOF是Go中最重要的错误变量,用于表示输入流的结尾,因为每个文件都有一个结尾,所以io.EOF很多时候并不能算是一个错误,更重要是表示输入流结束了。
var EOF = errors.New("EOF")

io.Reader

  • io.Reader表示一个读取器,将数据从某个资源读取到传输缓冲区。在缓冲区中,数据可以被流式传输和使用。
io.Reader
  • 对于要用作读取器的类型,必须实现io.Reader接口的唯一方法Read(p []byte)。只要实现了Read(p []byte)就是一个读取器
type Reader interface{
  Read(p []byte) (n int, err error)
}
  • Read()方法具有两个返回值,若资源内容全部读取完毕,应该返回io.EOF错误。
返回值 类型 描述
n int 读取到的字节数
err error 发生错误时的错误

利用io.Reader可以实现流式数据传输,Reader方法内部是被循环调用的,每次迭代,会从数据源读取一块数据放入缓冲区p中,直到返回io.EOF错误时停止。

例如:创建字符串读取器,流式地按字节读取。

str := "clear is better than clever"
//创建字符串读取器
reader := strings.NewReader(str)
//字节缓存区
buf := make([]byte, 4)
//循环读取
for{
    //读取到缓存区
    n,err := reader.Read(buf)
    if err!=nil{
        //读取结束
        if err!=io.EOF{
            fmt.Println("EOF:", n)
            break
        }
        fmt.Println(err)
        os.Exit(1)
    }

    result := string(buf[:n])
    fmt.Println(n, result)
}
4 clea
4 r is
4  bet
4 ter 
4 than
4  cle
3 ver
EOF

例如:实现从流中过滤掉非字母字符

//过滤函数
func alpha(c byte) byte{
    if (c>='a'&&c<='z') || (c>='A'&&c<='Z'){
        return c
    }
    return 0
}
type AlphaReader struct{
    src string //资源
    i int//当前读取的位置
}
func NewAlphaReader(src string) *AlphaReader{
    return &AlphaReader{src:src}
}
func (r *AlphaReader) Read(p []byte) (int,error){
    //判断是否已经读取到结尾
    if r.i >= len(r.src){
        return 0, io.EOF
    }
    //剩余未读取长度
    x := len(r.src) - r.i
    //缓存区填满大小
    bound := 0
    //判断剩余长度是否超过缓存区大小
    if x >= len(p){
        //完全填满缓冲区
        bound = len(p)
    }else if x < len(p){
        //缓存区无法补满
        bound = x
    }
    //创建缓存区
    buf := make([]byte, bound)
    //迭代读取
    i := 0
    for bound > i {
        //每次读取一个字节
        char := r.src[r.i]
        //执行过滤函数
        if c := alpha(char); c!=0{
            buf[i] = c
        }
        i++
        r.i++
    }
    //将缓存内容赋值到
    copy(p, buf)
    return i,nil
}

测试

str := "Hello! It's 9am, where is the sun?"

buf := make([]byte, 4)
reader := NewAlphaReader(str)
for {
    n,err := reader.Read(buf)
    if err==io.EOF{
        break
    }
    data := string(buf[:n])
    fmt.Print(data)
}
  • 组合多个Reader可重用和屏蔽下层实现的复杂度

标准库已经实现了多个Reader,可使用一个Reader作为另一个Reader的实现是一种常见用法,可让一个Reader重用另一个Reader的逻辑。

例如:修改AlphaReader以接受io.Reader作为其源

//过滤函数
func alpha(c byte) byte{
    if (c>='a'&&c<='z') || (c>='A'&&c<='Z'){
        return c
    }
    return 0
}
type AlphaReader struct{
    reader io.Reader//组合标准读取器
}
func NewAlphaReader(reader io.Reader) *AlphaReader{
    return &AlphaReader{reader:reader}
}
func (r *AlphaReader) Read(p []byte) (int,error){
    n,err := r.reader.Read(p)
    if err!=nil{
        return n,err
    }

    buf := make([]byte, n)
    for i:=0; i<n; i++{
        char := alpha(p[i])
        if char != 0{
            buf[i] = char
        }
    }
    copy(p, buf)

    return n,nil
}

测试

str := "Hello! It's 9am, where is the sun?"
reader := strings.NewReader(str)

buf := make([]byte, 4)
r := NewAlphaReader(reader)
for {
    n,err := r.Read(buf)
    if err==io.EOF{
        break
    }
    data := string(buf[:n])
    fmt.Print(data)
}

例如:与os.File结束过滤掉文件中的非字母字符

$ vim ./test.txt
Hello! It's 9am, where is the sun?
file,err := os.Open("./test.txt")
if err!=nil{
    fmt.Println(err)
    os.Exit(1)
}
defer file.Close()

r := NewAlphaReader(file)
buf := make([]byte, 4)
for {
    n,err := r.Read(buf)
    if err==io.EOF{
        break
    }
    data := string(buf[:n])
    fmt.Print(data)
}

io.Writer

  • io.Writer表示一个编写器,从缓存区读取数据,并将数据写入目标资源。
io.Writer
  • 对于要作为编写器的类型必须实现io.Writer接口唯一的方法Write(p []byte),只要实现了Write(p []byte)就是一个编写器。
type Writer interface{
  Write(p []byte) (n int, err error)
}
  • Write()方法具有两个返回值,一个是写入到目标资源的字节数,一个是发生错误时的错误。
返回值 类型 描述
n int 写入到目标资源的字节数
err error 发生错误时的错误

例如:使用bytes.Buffer类型作为io.Writer将数据写入内存缓存区

proverbs := []string{
    "many hands make light work",
    "measure for measure",
    "murder will out",
    "never say die",
}

var writer bytes.Buffer
for _,p := range proverbs{
    n,err := writer.Write([]byte(p))
    if err!=nil{
        fmt.Println(err)
        os.Exit(1)
    }
    if n != len(p){
        fmt.Println("failed to write data")
        os.Exit(1)
    }
}
result := writer.String()
fmt.Println(result)

例如:实现自定义的io.Writer将其内容作为字节序列写入通道

//ChanWriter 通道写入器
type ChanWriter struct{
    ch chan byte//目标资源
}
//NewChanWriter 创建通道写入器
func NewChanWriter() *ChanWriter{
    return &ChanWriter{ch:make(chan byte, 1024)}
}
//Chan 获取目标资源
func (w *ChanWriter) Chan() <-chan byte{
    return w.ch
}
//Close 关闭目标资源
func (w *ChanWriter) Close(){
    close(w.ch)
}
//Write 写入目标资源
func (w *ChanWriter) Write(p []byte) (int, error){
    n := 0
    //遍历输入数据按字节写入目标资源
    for _,b := range p{
        w.ch<-b
        n++
    }
    return n,nil
}

测试

writer := NewChanWriter()

go func(){
    defer writer.Close()
    writer.Write([]byte("money can talk"))
}()

for char := range writer.Chan(){
    fmt.Printf("%c\n", char)
}

io.Closer

  • Closer是包装基本Close()方法的接口用于关闭数据流,第一次调用后Closer的行为是未定义的。
  • 文件、归档(压缩包)、数据库连接、Socket等均需手动关闭的资源都实现了Closer接口。
  • 编程中经常会将Close方法的调用放在defer语句中
type Closer interface{
  Close() error //关闭数据流
}

例如:

file,err := os.Open("test.txt")
if err!=nil{

}
defer file.Close()

io.ReadCloser

  • response.Body的类型为io.ReadCloser

io.WriteCloser

io.ReadWriteCloser


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

本文来自:简书

感谢作者:JunChow520

查看原文:Go io

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

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