go io 包的使用(TeeReader, MultiReader, MultiWriter, Pipe)

AF驱动调试日志全解析:从配置到问题排查 本文全面解析AF驱动调试日志的配置、抓取与分析方法。从内核驱动与HAL层配置入手,确保日志正常输出,并详细解读开机初始化、马达识别、I2C通信等关键日志节点。通过多个实战案例,如对焦失败、前摄无功能、参数不生效及I2C报错等,系统性地展示了如何利用日志定位并解决AF对焦问题,是摄像头模组开发与调试的实用指南。 阅读详情

背景

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

TeeReader

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

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

源码
type teeReader struct {
	r Reader
	w 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.readers
				continue
			}
		}
		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 GC
			mr.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 = ErrShortWrite
			return
		}
	}
	return len(p), nil
}
简单示例
func tMultiWriter() {
	var buf []byte
	w1 := 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 operations
	wrCh chan []byte
	rdCh chan int

	once sync.Once // Protects closing done
	done chan struct{}
	rerr onceError
	werr onceError
}

函数返回的 PipeReader 和 PipeWriter 均有 Close 和 CloseWithError 方法,用于停止读写(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.rdCh
			b = b[nw:]
			n += nw
		case <-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 <- nr
		return nr, nil
	case <-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 的绝不先做(写->读->写->读->…)
银河麒麟操作系统最佳实践专栏介绍 本专栏聚焦国产银河麒麟操作系统(桌面版/服务器版)的实战应用,提供系统安装、配置优化、应用部署、故障排查等实用指南。内容基于真实操作场景,涵盖Linux通用技术(如RAID配置、环境变量管理)、服务器系统专项(如数据库迁移、驱动安装、安全加固)及桌面系统技巧(如PXE安装、镜像封装)。面向IT运维、开发者和国产化转型人员,突出实战性和差异化,帮助用户规避常见问题,提升系统管理效率。所有教程均配有详细步骤和截图,确保可操作性,并持续更新适配新版系统。 阅读详情

相关推荐

《矩阵论》学习笔记(四):4.2 矩阵的QR分解

《矩阵论》学习笔记(四):4.2 矩阵的QR分解 文章目录《矩阵论》学习笔记(四):4.2 矩阵的QR分解一. Givens矩阵与Givens变换1.1. 概念与理解1.2. Givens矩阵求解方法二. Householder矩阵与Householder变换2.1. 概念与理解2.2. Householder矩阵求解方法2.3. Givens变换与Householder变换的关系三. 矩阵的QR...

shine的博客 3681

go语言内置io包中TreeReader函数的理解和使用示例

在go语言的内置io包中的这个 TreeReader函数,函数原型func TeeReader(r Reader, w Writer) Reader 从函数原型中看是给他一个Reader, 和一个Writer 然后他给你返回一个Reader, 本文中我们把这个返回的Reader叫做 treeReader, 他是一个很特别的reader,。 他的作用就是将你输入的 r 和w建立一个对应关系,当你在读取这个treeReader的时候,他会将读取到的数据同时写入到w中。同时在他的内部没

Tekin 是深耕技术 20 年的全栈实战派专家,精通 Go/Python/Java 等多语言开发。博客专注技术原理与实战结合,深度解析 Python 高阶编程、Go 语言架构、数据库优化等硬核内容。涵盖并发编程、机器学习、云原生等前沿领域,通过真实案例拆 943

软考中级信息系统监理师(第二版)-第21章基础设施工程监理

由于网络信息技术的飞速发展,变化日新月异,网络设计不但要保证目前网络传输容量的要求,也要考虑今后网络的发展,满足当前以及今后相当一段时间内的网络需求,便于向新技术的升级和衔接,同时又需要保护原有的投资。通用布缆系统工程中,承建单位的竣工文档主要包括以下内容:承建合同,招标文件,投标文件,设计方案,工程实施组织设计方案,工程施工报告,隐蔽工程记录,变更记录,自测报告,信息点分布图(表),园区、楼宇、水平配线表,楼层平面图及无线设备点位图,无线设备信息表,竣工图纸等。多排安装时,列间距不应小于1200mm。

2301_78583973的博客 1402

golang io.TeeReader函数使用,实现一个实时下载进度条

golang io.TeeReader函数使用,实现一个实时下载进度条。

gitxuzan 895

STM32F407实现LwIP的MQTT客户端并连接到服务器

这个是用LwIP协议栈中自带的MQTT客户端API写成的,代码注释很详细,附带有服务端的展示。LwIP版本是2.1.2.

wait_for_STM32的博客 6399

从零开始:手把手教你做转录组与代谢组的联合分析(含R代码)

本文详细介绍了转录组与代谢组联合分析的全流程,包括数据准备、预处理、差异分析及可视化方法,并提供了实用的R代码示例。通过多组学整合分析,研究者可以更全面地理解生物过程,适用于疾病机制和药物开发等医学研究领域。

weixin_29306317的博客 439

golang下载、上传文件MD5高效计算方法,利用io.TeeReader函数特性 实时计算文件md5签名

在go语言的开发中,当我们在操作下载或者上传文件对象时, 我们可以利用golang内置的io包中的 TeeReader函数特性,高效实时计算文件的md5值。 方法如下......

Tekin 是深耕技术 20 年的全栈实战派专家,精通 Go/Python/Java 等多语言开发。博客专注技术原理与实战结合,深度解析 Python 高阶编程、Go 语言架构、数据库优化等硬核内容。涵盖并发编程、机器学习、云原生等前沿领域,通过真实案例拆 1072

Tesseract in Java - Intellij IDEA对Tess4J的配置和使用

最近要弄OCR,搜到这个,官网全英文,很烦,看看有没有中文的博客介绍,擦,一个比一个奇葩,还是自己按照官方的来吧,外网慢的很,传到csdn给大家下载。中文训练库下载地址: http://download.youkuaiyun.com/detail/d_dmelon/9916532Tess4J3.4下载地址: http://download.youkuaiyun.com/detail/d_dmelon/9916519安装

Lewis的博客 1万+

go语言io reader_Golang io.TeeReader()用法及代码示例

在Go语言中,io软件包为I /O原语提供基本接口。它的主要工作是封装此类原始之王的正在进行的实现。 Go语言中的TeeReader()函数用于返回“Reader”,该“Reader”从声明的“r”读取,然后将其写入声明的“w”。然后,将通过声明的“r”执行的所有读取与对声明的“w”的等效写入进行比较。此方法不需要任何内部缓冲,并且读取完成后即可完成写入。而且,此功能在io包下定义。在这里,您需要...

weixin_33216825的博客 1210

智驾车载以太网(18)-switch之报文转发和vlan处理流程

本文详细解析了交换机中报文处理和转发流程:1)通过目标MAC查找转发端口,源MAC创建ARL条目;2)VLAN标签处理包括tagged/untagged帧判断、默认VID标记等5种处理方式;3)完整转发流程包含CRC校验、IQ封装、VLAN和ARL表查询、端口向量计算及优先级队列发送。文中还涉及TCAM修改VLAN、802.1q多种模式处理等细节,省略了流量控制等次要流程。该过程实现了基于MAC和VLAN的高效二层交换。

mitugaoyang1993的博客 44

Win11 系统下 VisualStudio2022 配置点云库 PCL1.11.1

安装完成之后打开文件夹E:\PCL 1.11.1\3rdParty\OpenNI2双击 并选择路径 (E:\PCL 1.11.1\3rdParty\OpenNI2) 安装即可全部安装完成之后,将pcl-1.11.1-pdb-msvc2019-win64.zip解压后的 .pdb文件拷贝到(E:\PCL 1.11.1\bin)中设置环境变量:到此,环境变量配置完成。

qq_53545309的博客 1130

一文掌握 Go 语言 I/O 操作中的 io.Reader 和 io.Writer

io.Reader:用于从数据源读取数据,Read方法将数据读入给定的字节切片。io.Writer:用于将数据写入目标,Write方法将数据写入指定的目标。通过 `

每日一贴 1624

手把手教你学simulink(8.39)--基于Simulink的步进电机(Stepper Motor)仿真实例

通过这个仿真实例,你可以学习如何使用Simulink构建和仿真步进电机(Stepper Motor)系统。你可以根据实际需求调整模型和参数,进行更复杂的仿真和分析。希望这个例子对你有所帮助,祝你学习愉快!

MHD0815的博客 1053

go标准库1-io

go中的IO操作主要由io、io/ioutil、fmt、bufio实现,他们各有自己的特点 io 为 IO原语提供基本的接口,他的方法都是异步io,并不保证所有的方法都是并发安全的 io/ioutil 主要提供了对文件操作的辅助io和一些常用、方便的IO操作函数 bufio 基本的io原语函数调用都需要传入一个缓冲字节,bufio 提供了一个默认的缓冲字节长度,并实现了 io 中常用的接口,可以...

康搁搁的小黑屋 430

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

go标准库io、ioutil

标准库io io.WriteString s输出到w func WriteString(w Writer, s string) (n int, err error) io.Copy 自动以32k字节的buf进行遍历复制直到遇到io.EOF。返回总共复制的字节数,如果成功err返回nil func Copy(dst Writer, src Reader) (written ...

sinat_26682309的博客 479

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

Golang标准库——io-结构

结构 LimitedReader 定义 限制从Reader中读取的字节数。 type LimitedReader struct { R Reader // underlying reader N int64 // max bytes remaining } 方法 //Limited func (l *LimitedReader) Read(p []byte) (n i...

diekuangzhen0210的博客 235

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

【速查版】Go 标准库:io

https://golang.org/pkg/io/ 官方标准库文档不好阅读,遂整理了下,方便自己快速查阅。

alenliu's blog 765

Golang学习(8)——io 包

Golang学习-io包--------------------先说一下接口,Go 语言中的接口很简单,在 Go 语言的 io 包中有这样一个函数:funcReadFull(r Reader, buf []byte) (n int, err error)这个函数可以把对象 r 中的数据读出来,然后存入一个缓冲区 buf 中,以便其它代码可以处理 buf 中的数据。这里...

weixin_34067049的博客 156

Go语言接口与多态

接口是Go语言最核心的特性之一,也是Go实现多态和抽象的关键机制。本文参考了Go官方博客(go.dev/blog/laws-of-reflection)、Effective Go文档以及Go语言规范,系统地讲解了接口的定义、隐式实现、内部结构、类型断言和接口组合等核心概念。

星航夜空的帆舟 412

go语言io reader_【已解决】go语言中如何使用io的MultiWriter

【背景】折腾:期间,需要去搞懂:如果新建和设置MultiWriter。【折腾过程】1.参考:去看看:2.然后去试试代码:var filenameOnly stringfilenameOnly = GetCurFilename()fmt.Println("filenameOnly=", filenameOnly)var logFilename string = filenameOnly + ".l...

weixin_35551323的博客 838

go语言io reader_Go 语言显示下载文件进度/分段读取 io.TeeReader()

在 Go 语言下载文件 http.Get() 和 io.Copy() 章节我们讲解了如何使用 Go 实现一个下载器,可以实现下载任何大小的任意文件。一般情况下,这个下载器已经足够使用了,但是,在下载文件时,我们往往想知道当前进度是多少。这时候,直接使用 io.Copy() 就无能为了。实现下载文件进度的方法其实很简单,就是实现分段读取响应流。因为响应是一个流 ( stream ) ,就像水管里的水...

weixin_35867608的博客 868

Go 标准库

io.Reader用于读取数据,io.Writer用于写入数据。可以将读取的数据同时写入另一个io.Writer。可以将写入的数据同时写入多个io.Writer。通过结合使用这些工具,你可以轻松地处理 I/O 操作并实现类似“复制”或“分发”数据的功能。—注释结束—是的,没错。你看这些包,它们是按字母顺序排列的,如果你知道自己要找什么,那很好。但我觉得有趣的是,把它们分门别类,理解它们的子分类以及它们的外观。我觉得这让它们更容易理解和应用。我喜欢这个过程中传达出的潜在信息---

techdashen的博客 854

lucene学习笔记

1.分布搜索(多目录搜索) 我们可以使用 multireader 或 multisearcher 搜索多个索引库。 multireader reader = new multireader(new indexreader[] { indexreader.open(@”c:\index”), indexreader.open(@”\\server\index”) }); indexsearch

duanduanpeng的专栏 565

Collector的使用和MultiReader的使用

Lucene版本用的是3.5.0,虽然版本比较老,但是Zoie支持这个版本。

ningbohezhijun的专栏 1808

go io.Pipe()使用详解

文章目录io.Pipe()定义io.Pipe使用io.Pipe在进程通讯中使用 io.Pipe()定义 func Pipe() (*PipeReader, *PipeWriter) io.Pipe使用 io.Pipe会返回一个reader和writer,对reader读取(或写入writer)后,进程会被锁住,直到writer有新数据流进入或关闭(或reader把数据读走)。如下面程序会出现死锁。 package main import ( "io" ) func main() { read..

爱死亡机器人 4390

go源码库学习之io库

go源码库学习之io库

liao__ran的博客 436
上一篇: go 中,json 转换时间(time.Time)的格式(默认格式为 RFC3339)
下一篇: go http.Post 指定 body 单次读取的大小(32k?)
Grassto
博客等级 码龄9年 213粉丝 137原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值