背景

今天在做文件的上传,在文件上传结束后,需对比上传文件和本地文件的哈希值是否一致。
对上传客户端来说,可以提前单独计算好文件的哈希值,然后在上传成功后,与服务端返回的哈希值进行对比。但显然,这并不是一个理想的方案,边上传边计算,这样是比较合理的。
我这里一开始使用的是 io 包的 Pipe() 方法来做的,之前一直都没怎么用过,比较冷门,顺带着就看了下源码实现,再带着就看了下 io 包的其他方法的使用,最后引申出了本篇。

TeeReader

func TeeReader(r Reader, w Writer) Reader {return &teeReader{r, w}
}

边读边写,函数返回的 reader 会在从接收参数 r 中读取内容的同时,将内容写入 w

源码

type teeReader struct {r Readerw Writer
}func (t *teeReader) Read(p []byte) (n int, err error) {n, err = t.r.Read(p)if n > 0 {if n, err := t.w.Write(p[:n]); err != nil {return n, err}}return
}

简单示例

可以简化代码,如:计算文件哈希 (这里使用不一定合理,仅打个比方)

func TeeGetFileMD5(path string) (string, error) {file, err := os.Open(path)if err != nil {return "", err}h := md5.New()tr := io.TeeReader(file, h)_, err = io.ReadAll(tr) // 优化if err != nil {return "", err}return fmt.Sprintf("%x", h.Sum(nil)), nil
}

MultiReader

func MultiReader(readers ...Reader) Reader {r := make([]Reader, len(readers))copy(r, readers)return &multiReader{r}
}

顺序读取所有的 reader,直到出错或者所有的 reader 都读取完成后返回 EOF

源码

type multiReader struct {readers []Reader
}func (mr *multiReader) Read(p []byte) (n int, err error) {for len(mr.readers) > 0 {// Optimization to flatten nested multiReaders (Issue 13558).if len(mr.readers) == 1 { // 适配multiReader的嵌套if r, ok := mr.readers[0].(*multiReader); ok {mr.readers = r.readerscontinue}}n, err = mr.readers[0].Read(p)if err == EOF { // 读完一个,从数组剔除,换下一个// Use eofReader instead of nil to avoid nil panic// after performing flatten (Issue 18232).mr.readers[0] = eofReader{} // permit earlier GCmr.readers = mr.readers[1:]}if n > 0 || err != EOF {if err == EOF && len(mr.readers) > 0 { // 是EOF,读到最后一个再返回EOF// Don't return EOF yet. More readers remain.err = nil}return }}return 0, EOF
}

简单示例

func tMultiReader() {r1 := bytes.NewReader([]byte("ABC"))r2 := bytes.NewReader([]byte("DEF"))reader := io.MultiReader(r1, r2)var buf = make([]byte, 1)for {n, err := reader.Read(buf)if err != nil {if err == io.EOF {return }fmt.Println(err)return}fmt.Println(string(buf[:n])) // ABCDEF}
}

MultiWriter

func MultiWriter(writers ...Writer) Writer {allWriters := make([]Writer, 0, len(writers))for _, w := range writers {if mw, ok := w.(*multiWriter); ok {allWriters = append(allWriters, mw.writers...)} else {allWriters = append(allWriters, w)}}return &multiWriter{allWriters}
}

写一即多,函数返回的 writer 进行写操作时,会对所有的入参 writer 都进行写操作(copy)。当有多个输出点的时候,直接使用它会简化不少代码。

源码

type multiWriter struct {writers []Writer
}func (t *multiWriter) Write(p []byte) (n int, err error) {for _, w := range t.writers {n, err = w.Write(p)if err != nil {return}if n != len(p) {err = ErrShortWritereturn}}return len(p), nil
}

简单示例

func tMultiWriter() {var buf []bytew1 := bytes.NewBuffer(buf)w2 := bytes.NewBuffer(buf)writer := io.MultiWriter(w1, w2)_, err := writer.Write([]byte("123"))if err != nil {fmt.Println(err)return}w1Res, err := ioutil.ReadAll(w1)fmt.Println(string(w1Res), err) // 123 <nil>w2Res, err := ioutil.ReadAll(w2)fmt.Println(string(w2Res), err) // 123 <nil>
}

Pipe

func Pipe() (*PipeReader, *PipeWriter) {p := &pipe{wrCh: make(chan []byte),rdCh: make(chan int),done: make(chan struct{}),}return &PipeReader{p}, &PipeWriter{p}
}
type pipe struct {wrMu sync.Mutex // Serializes Write operationswrCh chan []byterdCh chan intonce sync.Once // Protects closing donedone chan struct{}rerr onceErrorwerr onceError
}

函数返回的 PipeReaderPipeWriter 均有 CloseCloseWithError 方法,用于停止读写(done)。

  • wrCh 写入的数据
  • rdCh 读了多少了
  • once 只close一次done
  • done 结束标志
  • rerr 读错
  • werr 写错

源码

func (p *pipe) Write(b []byte) (n int, err error) {select {case <-p.done:return 0, p.writeCloseError()default:p.wrMu.Lock()defer p.wrMu.Unlock()}for once := true; once || len(b) > 0; once = false {select {case p.wrCh <- b:nw := <-p.rdChb = b[nw:]n += nwcase <-p.done:return n, p.writeCloseError()}}return n, nil
}func (p *pipe) Read(b []byte) (n int, err error) {select {case <-p.done:return 0, p.readCloseError()default:}select {case bw := <-p.wrCh:nr := copy(b, bw)p.rdCh <- nrreturn nr, nilcase <-p.done:return 0, p.readCloseError()}
}

ReadWrite 方法的开头都有如下一段代码。由于 channelcase 选择是随机的,需要确保没有结束再进行 ReadWrite 操作。

 select {case <-p.done:return 0, p.writeCloseError()default:p.wrMu.Lock()defer p.wrMu.Unlock()}

简单示例

func tPipe() {r, w := io.Pipe()go func  () {for i := 0; i < 3 ; i++ {fmt.Println("write now!")n, err := w.Write([]byte("hello"))if err != nil {fmt.Println("write err:", err.Error())} else {fmt.Println("write end n:", n)}}w.Close()}()//time.Sleep(time.Second)b := make([]byte, 100)for {n, err := r.Read(b)if err != nil {if err != io.EOF {fmt.Println("read err:", err.Error())}break} else {fmt.Println("read:", string(b[:n]))}}
}
// write now!
// read: hello
// write end n: 5
// write now!
// read: hello
// write end n: 5
// write now!
// read: hello
// write end n: 5

TeeReaderMultiWriter 的结合使用

例子:复制文件,并计算文件的哈希值

func copyFileWithHash() {f, dstF, hashW, err := getTestRW()if err != nil {fmt.Println(err)return}now := time.Now()defer func() {fmt.Println("耗时:", time.Now().Sub(now))}()multiW := io.MultiWriter(dstF, hashW)teeR := io.TeeReader(f, multiW)buf := make([]byte, 512)for {_, err := teeR.Read(buf)if err == io.EOF {break}utils.CheckErr(err)}fmt.Printf("文件sha256:%x\n",  hashW.Sum(nil))// 文件大小: 1840640// 文件sha256:b61ec80071fc414c44ff1a05f323679f9fc3e7caa2a68363019663fc16677568// 耗时: 15.881ms
}func getTestRW() (f, dstF *os.File, shaW hash.Hash, err error) {f, err = os.Open(`E:\test\html报告-1628662433.tar`)if err != nil {return}fInfo, err := f.Stat()if err != nil {return}fmt.Println("文件大小:", fInfo.Size())dstF, err = os.Create(`E:\test\1.tar`)if err != nil {return}shaW = sha256.New()return
}

总结

io 包的一些方法还是挺好用的,实现都并不是很复杂,感兴趣的可以看下各个方法的具体实现。本篇抛砖引玉,只是做了简单的介绍,实际开发过程中,不使用这些方法也是能够完全达成目的的,可能就是稍微繁琐点。

  • TeeReader 的一心二用(边读编写)
  • MultiReader 的先来后到(顺序读取)
  • MultiWriter 的同甘共苦(写一即多)
  • Pipe 的绝不先做(写->读->写->读->…)

go io 包的使用(TeeReader, MultiReader, MultiWriter, Pipe)相关推荐

  1. golang 中io包用法(一)

    本文转自Golove博客:http://www.cnblogs.com/golove/p/3276678.html   ,并在此基础上进行修改. io 包为I/O原语提供了基础的接口.它主要包装了这些 ...

  2. java.io包对象读写_java.io 包中的____________和____________类主要用于对对象(Object)的读写_学小易找答案...

    [多选题]连铸钢水成分控制的要求有( ). [单选题]起动机用直流电动机将电能转化为 [单选题]下列关于我国少数民族传统禁忌的说法中,错误的是( ). [多选题]下列选项中 , 属于表单控件的是 ( ...

  3. 装饰器模式与java.io包

    为什么80%的码农都做不了架构师?>>>    Decorator设计模式是典型的结构型模式(在GOF的那本模式的Bible中将模式分为:1.创建型模式:2.结构型模式:3.行为模式 ...

  4. Java.io包中常用的类

    Java.io包 Java.io.BufferedInputStream类实例 Java.io.BufferedOutputStream类使用例子 Java.io.BufferedReader类 Ja ...

  5. Java源码解析——Java IO包

    一.基础知识: 1. Java IO一般包含两个部分:1)java.io包中阻塞型IO:2)java.nio包中的非阻塞型IO,通常称为New IO.这里只考虑到java.io包中堵塞型IO: 2. ...

  6. 【JDK源码】java.io包常用类详解

    看完java.io的JDK源码,在网上发现一篇关于java.io中的类使用的文章总结的很全面,看完之后在原文的基础上加了一些自己的总结如下构成了本篇文章.原文地址 一.Java Io流 1. Java ...

  7. spring源码分析-core.io包里面的类

    前些日子看<深入理解javaweb开发>时,看到第一章java的io流,发觉自己对io流真的不是很熟悉.然后看了下JDK1.7中io包的一点点代码,又看了org.springframewo ...

  8. Java学习笔记之 IO包 字节流

    IO包最重要的五个类和一个接口 File/OutputStream/InputStream(字节流)/Writer/Reader(字符流) 一个接口:Serializable File类: 字节流: ...

  9. 封装成jar包_通用源码阅读指导mybatis源码详解:io包

    io包 io包即输入/输出包,负责完成 MyBatis中与输入/输出相关的操作. 说到输入/输出,首先想到的就是对磁盘文件的读写.在 MyBatis的工作中,与磁盘文件的交互主要是对 xml配置文件的 ...

最新文章

  1. 软件外包是中国程序员的大金矿,大家一起来挣×××吧
  2. ZOJ 2165 Red and Black
  3. java 当中的验证码_Java Web中验证码的实现
  4. SAP License:SAP软件功能有哪些?
  5. Comet OJ - Contest #11 题解赛后总结
  6. python中的怎么用,python中怎么用#号
  7. 【实践】地理探测器GeoDetector软件使用
  8. 如何自定义 conventional-changelog
  9. 数据分析与AI(七)傅里叶对登月图片降噪/scipy库对图片进行处理/
  10. 概念:伪随机数、种子以及C中的随机函数
  11. 毛桃pe系统 linux,老毛桃U盘PE重装教程
  12. linux下使用VSCode的C++添加json问题。
  13. 自定义View进阶-手绘地图(二)
  14. 【转】NAS群晖DSM 5.0-4458安装教程
  15. Latex 中图片格式转换
  16. 二叉查找树与红黑树原理和程序全面介绍
  17. AR增强现实的三大关键技术
  18. 零基础无实物一步一步学PLCS7-1200仿真(三)-电动机正反转控制(互锁)
  19. vue路由匹配实现包容性_简约设计,最大利润,包容性问题
  20. ArcGISEngine学习(1)

热门文章

  1. 使用pgAdmin把Excel文件导入PostgreSql数据库
  2. R语言|导入excel数据
  3. 睁眼、耸肩、觉醒:人形机器人的吊诡与最终幻想
  4. 极速office(Word)人民币符号怎么打出来
  5. EKL构造点和线的基本用法
  6. PHP独立ip统计,通过nginx日志统计独立ip的个数 新乡独立ip 独立ipvps 独立公网i
  7. vue判断有没有滚动条
  8. MATLAB---三维绘制函数实例介绍
  9. java hash 数组_Java数组 哈希表 属性类 -解道Jdon
  10. 17. 如何通过 SAP ABAP OData $expand 操作在同一个 HTTP 请求中返回多个节点的数据