go io 包的使用(TeeReader, MultiReader, MultiWriter, Pipe)
背景
今天在做文件的上传,在文件上传结束后,需对比上传文件和本地文件的哈希值是否一致。
对上传客户端来说,可以提前单独计算好文件的哈希值,然后在上传成功后,与服务端返回的哈希值进行对比。但显然,这并不是一个理想的方案,边上传边计算,这样是比较合理的。
我这里一开始使用的是 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
}
函数返回的 PipeReader
和 PipeWriter
均有 Close
和 CloseWithError
方法,用于停止读写(done
)。
wrCh
写入的数据rdCh
读了多少了once
只close一次donedone
结束标志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()}
}
Read
和 Write
方法的开头都有如下一段代码。由于 channel
的 case
选择是随机的,需要确保没有结束再进行 Read
或 Write
操作。
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
TeeReader
和 MultiWriter
的结合使用
例子:复制文件,并计算文件的哈希值
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)相关推荐
- golang 中io包用法(一)
本文转自Golove博客:http://www.cnblogs.com/golove/p/3276678.html ,并在此基础上进行修改. io 包为I/O原语提供了基础的接口.它主要包装了这些 ...
- java.io包对象读写_java.io 包中的____________和____________类主要用于对对象(Object)的读写_学小易找答案...
[多选题]连铸钢水成分控制的要求有( ). [单选题]起动机用直流电动机将电能转化为 [单选题]下列关于我国少数民族传统禁忌的说法中,错误的是( ). [多选题]下列选项中 , 属于表单控件的是 ( ...
- 装饰器模式与java.io包
为什么80%的码农都做不了架构师?>>> Decorator设计模式是典型的结构型模式(在GOF的那本模式的Bible中将模式分为:1.创建型模式:2.结构型模式:3.行为模式 ...
- Java.io包中常用的类
Java.io包 Java.io.BufferedInputStream类实例 Java.io.BufferedOutputStream类使用例子 Java.io.BufferedReader类 Ja ...
- Java源码解析——Java IO包
一.基础知识: 1. Java IO一般包含两个部分:1)java.io包中阻塞型IO:2)java.nio包中的非阻塞型IO,通常称为New IO.这里只考虑到java.io包中堵塞型IO: 2. ...
- 【JDK源码】java.io包常用类详解
看完java.io的JDK源码,在网上发现一篇关于java.io中的类使用的文章总结的很全面,看完之后在原文的基础上加了一些自己的总结如下构成了本篇文章.原文地址 一.Java Io流 1. Java ...
- spring源码分析-core.io包里面的类
前些日子看<深入理解javaweb开发>时,看到第一章java的io流,发觉自己对io流真的不是很熟悉.然后看了下JDK1.7中io包的一点点代码,又看了org.springframewo ...
- Java学习笔记之 IO包 字节流
IO包最重要的五个类和一个接口 File/OutputStream/InputStream(字节流)/Writer/Reader(字符流) 一个接口:Serializable File类: 字节流: ...
- 封装成jar包_通用源码阅读指导mybatis源码详解:io包
io包 io包即输入/输出包,负责完成 MyBatis中与输入/输出相关的操作. 说到输入/输出,首先想到的就是对磁盘文件的读写.在 MyBatis的工作中,与磁盘文件的交互主要是对 xml配置文件的 ...
最新文章
- 软件外包是中国程序员的大金矿,大家一起来挣×××吧
- ZOJ 2165 Red and Black
- java 当中的验证码_Java Web中验证码的实现
- SAP License:SAP软件功能有哪些?
- Comet OJ - Contest #11 题解赛后总结
- python中的怎么用,python中怎么用#号
- 【实践】地理探测器GeoDetector软件使用
- 如何自定义 conventional-changelog
- 数据分析与AI(七)傅里叶对登月图片降噪/scipy库对图片进行处理/
- 概念:伪随机数、种子以及C中的随机函数
- 毛桃pe系统 linux,老毛桃U盘PE重装教程
- linux下使用VSCode的C++添加json问题。
- 自定义View进阶-手绘地图(二)
- 【转】NAS群晖DSM 5.0-4458安装教程
- Latex 中图片格式转换
- 二叉查找树与红黑树原理和程序全面介绍
- AR增强现实的三大关键技术
- 零基础无实物一步一步学PLCS7-1200仿真(三)-电动机正反转控制(互锁)
- vue路由匹配实现包容性_简约设计,最大利润,包容性问题
- ArcGISEngine学习(1)
热门文章
- 使用pgAdmin把Excel文件导入PostgreSql数据库
- R语言|导入excel数据
- 睁眼、耸肩、觉醒:人形机器人的吊诡与最终幻想
- 极速office(Word)人民币符号怎么打出来
- EKL构造点和线的基本用法
- PHP独立ip统计,通过nginx日志统计独立ip的个数 新乡独立ip 独立ipvps 独立公网i
- vue判断有没有滚动条
- MATLAB---三维绘制函数实例介绍
- java hash 数组_Java数组 哈希表 属性类 -解道Jdon
- 17. 如何通过 SAP ABAP OData $expand 操作在同一个 HTTP 请求中返回多个节点的数据