解释Go中常见的I/O模式

在这篇文章中,我想介绍一下常见的I/O(输入和输出)模式。我想在这篇文章中用浅显的语言澄清这些概念,这样人们就可以在他们的应用程序中使用优雅的Go I/O API。
让我们看看开发人员在日常生活中需要的常见用例。

写到标准输出

每个Go编程教程教给你的最常见的例子:

package mainimport "fmt"func main() {fmt.Println("Hello World")
}

但是,没有人告诉你上述代码是这个例子的简化版:

package mainimport ("fmt""os"
)func main() {fmt.Fprintln(os.Stdout, "Hello World")
}

这里发生了什么?我们有一个额外的包导入,叫做os,并且使用了fmt包中一个叫做Fprintln的方法。Fprintln方法接收一个io.Writer类型和一个要写入的字符串。os.Stdout满足io.Writer接口。
这个例子很好,可以扩展到除os.Stdout以外的任何写程序。

写到定制的writer

你学会了如何向os.stdout写入数据。让我们创建一个自定义writer并在那里存储一些信息。我们可以通过初始化一个空的缓冲区并向其写入内容来实现:

package mainimport ("bytes""fmt"
)func main() {// Empty buffer (implements io.Writer)var b bytes.Bufferfmt.Fprintln(&b, "Hello World") // Don't forget &// Optional: Check the contents storedfmt.Println(b.String()) // Prints `Hello World`
}

这个片段实例化了一个空缓冲区,并将其作为Fprintln方法的第一个参数(io.Writer)。

同时写给多个writer

有时,人们需要将一个字符串写入多个writer中。我们可以使用io包中的MultiWriter方法轻松做到这一点。

package mainimport ("bytes""fmt""io"
)func main() {// Two empty buffersvar foo, bar bytes.Buffer// Create a multi writermw := io.MultiWriter(&foo, &bar)// Write message into multi writerfmt.Fprintln(mw, "Hello World")// Optional: verfiy data stored in buffersfmt.Println(foo.String())fmt.Println(bar.String())
}

在上面的片段中,我们创建了两个空的缓冲区,叫做foo和bar。我们将这些writer传递给一个叫做io.MultiWriter的方法,以获得一个组合写入器。消息Hello World将在内部同时被写入foo和bar中。

创建一个简单的reader

Go提供了io.Readerinterface来实现一个I/O reader。reader 不进行读取,但为他人提供数据。它是一个临时的信息仓库,有许多方法,如WriteTo, Seek等。
让我们看看如何从一个字符串创建一个简单的 reader:

package mainimport ("fmt""io""strings"
)func main() {// Create a new reader (Readonly)r := strings.NewReader("Hello World")// Read all content from readerb, err := io.ReadAll(r)if err != nil {panic(err)}// Optional: verify datafmt.Println(string(b))
}

这段代码使用strings.NewReader方法创建了一个新的 reader。该 reader 拥有io.Reader接口的所有方法。我们使用io.ReadAll从 reader 中读取内容,它返回一个字节切片。最后,我们将其打印到控制台。
注意:os.Stdin是一个常用的 reader,用于收集标准输入。

一次性从多个 reader 上读取数据

与io.MultiWriter类似,我们也可以创建一个io.MultiReader来从多个 reader 那里读取数据。数据会按照传递给io.MultiReader的读者的顺序依次收集。这就像一次从不同的数据存储中收集信息,但要按照给定的顺序。

package mainimport ("fmt""io""strings"
)func main() {// Create two readersfoo := strings.NewReader("Hello Foo\n")bar := strings.NewReader("Hello Bar")// Create a multi readermr := io.MultiReader(foo, bar)// Read data from multi readerb, err := io.ReadAll(mr)if err != nil {panic(err)}// Optional: Verify datafmt.Println(string(b))
}

代码很简单,创建两个名为foo和bar的 reader,并试图从它们中创建一个MultiReader。我们可以使用io.Readall来读取MultiReader的内容,就像一个普通的 reader。
现在我们已经了解了 reader 和 writer,让我们看看从 reader 复制数据到 writer 的例子。接下来我们看到复制数据的技术。
注意:不要对大的缓冲区使用io.ReadAll,因为它们会消耗尽内存。

将数据从 reader 复制到 writer

再次对定义的理解:
reader:我可以从谁那里复制数据
writer:我可以把数据写给谁?我可以向谁写数据
这些定义使我们很容易理解,我们需要从一个 reader(字符串阅读器)加载数据,并将其转储到一个 writer(如os.Stdout或一个缓冲区)。这个复制过程可以通过两种方式发生:

  • reader 将数据推送给 writer
  • writer 从 reader 中拉出数据

reader 将数据推送给 writer

这一部分解释了第一种拷贝的变化,即 reader 将数据推送到 writer 那里。它使用reader.WriteTo(writer)的API。

package mainimport ("bytes""fmt""strings"
)func main() {// Create a readerr := strings.NewReader("Hello World")// Create a writervar b bytes.Buffer// Push datar.WriteTo(&b) // Don't forget &// Optional: verify datafmt.Println(b.String())
}

在代码中,我们使用WriteTo方法从一个名为r的 reader 那里把内容写进写 writer b。在下面的例子中,我们看到一个 writer 如何主动地从一个 reader 那里获取信息。

writer 从 reader 中拉出数据

方法writer.ReadFrom(reader)被一个 writer 用来从一个给定的 reader 中提取数据。让我们看一个例子:

package mainimport ("bytes""fmt""strings"
)func main() {// Create a readerr := strings.NewReader("Hello World")// Create a writervar b bytes.Buffer// Pull datab.ReadFrom(r)// Optional: verify datafmt.Println(b.String())
}

该代码看起来与前面的例子相似。无论你是作为 reader 还是 writer,你都可以选择变体1或2来复制数据。现在是第三种变体,它比较干净。

使用 io.Copy

io.Copy是一个实用的函数,它允许人们将数据从一个 reader 移到一个 writer。
让我们看看它是如何工作的:

package mainimport ("bytes""fmt""io""strings"
)func main() {// Create a readerr := strings.NewReader("Hello World")// Create a writervar b bytes.Buffer// Copy data_, err := io.Copy(&b, r) // Don't forget &if err != nil {panic(err)}// Optional: verify datafmt.Println(b.String())
}

io.Copy的第一个参数是Writer(目标),第二个参数是Reader(源),用于复制数据。
每当有人将数据写入 writer 时,你希望有信息可以被相应的 reader 读取。这就出现了管道的概念。

用io.Pipe创建一个数据管道

io.Pipe返回一个 reader 和一个 writer,向 writer 中写入数据会自动允许程序从 reader 中消费数据。它就像一个Unix的管道。
你必须把写入逻辑放到一个单独的goroutine中,因为管道会阻塞 writer,直到从 reader 中读取数据,而且 reader 也会被阻塞,直到 writer 被关闭。

package mainimport ("fmt""io"
)func main() {pr, pw := io.Pipe()// Writing data to writer should be in a go-routine// because pipe is synchronous.go func() {defer pw.Close() // Important! To notify writing is donefmt.Fprintln(pw, "Hello World")}()// Code is blocked until someone writes to writer and closes itb, err := io.ReadAll(pr)if err != nil {panic(err)}// Optional: verify datafmt.Println(string(b))
}

该代码创建了一个管道reader和管道writer。我们启动一个程序,将一些信息写入管道writer并关闭它。我们使用io.ReadAll方法从管道reader中读取数据。如果你不启动一个单独的程序(写或读,都是一个操作),程序将陷入死锁。
我们在下一节讨论管道的一个更实际的使用案例。

用io.Pipe、io.Copy和io.MultiWriter捕捉函数的stdout到一个变量中。

假设我们正在构建一个CLI应用程序。作为这个过程的一部分,我们创建一个函数产生的标准输出(到控制台),并将相同的信息赋值到一个变量中。我们怎样才能做到这一点呢?我们可以使用上面讨论的技术来创建一个解决方案。

package mainimport ("bytes""fmt""io""os"
)// Your function
func foo(w *io.PipeWriter) {defer w.Close()// Write a message to pipe writerfmt.Fprintln(w, "Hello World")
}func main() {// Create a pipepr, pw := io.Pipe()// Pass writer to functiongo foo(pw)// Variable to get standard output of functionvar b bytes.Buffer// Create a multi writer that is a combination of// os.Stdout and our variable byte buffermw := io.MultiWriter(os.Stdout, &b)// Copies reader content to standard output_, err := io.Copy(mw, pr)if err != nil {panic(err)}// Optional: verify datafmt.Println(b.String())
}

上述程序是这样工作的:

  • 创建一个管道,给出一个 reader 和 writer。如果你向管道 writer 写了什么,Go会把它复制到管道的 reader 那里。
  • 创建一个 io.MultiWriter 与 os.Stdout 和自定义缓冲区 b
  • foo(作为一个协程)将把一个字符串写到管道 writer 中
  • io.Copy将把内容从管道 reader 复制到 MultiWriter 中。
  • os.Stdout将接收输出以及你的自定义缓冲区b
  • 内容现在可以在b中使用

使用Go中的iopackage,我们可以像我们在这些模式中看到的那样操作数据。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/651370.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

npm install出错的各种情况

1.npm不再支持签名证书,npm install 走的是http协议,需要通过数字证书保证 解决方法: 解决方法:1、取消ssl验证:npm config set strict-ssl false 这个方法一般就可以解决了。 2、更换npm镜像源:npm confi…

分布式数据实现跨设备数据同步的N个秘密 | 分布式数据管理解析(二)

上期我们给大家带来分布式数据管理如何完成数据存储,数据同步,数据跨端访问,并保证整个过程中跨设备数据安全的解读。 这都得益于分布式数据管理平台抽象出的三大关键技术——分布式数据库,分布式文件系统和融合搜索。 那么这三…

IS-IS:10 ISIS路由渗透

ISIS的非骨干区域,无明细路由,容易导致次优路径问题。可以引入明细路由。 在IS-IS 网络中,所有的 level-2 和 level-1-2 路由器构成了一个连续的骨干区域。 level-1区域必须且只能与骨干区域相连,不同 level-1 区域之间不能直接…

ACDSee 2024旗舰版 下载安装汉化教程,ACDSee 最新版,附安装包和工具,全网最简单,轻松搞的安装,无套路

前言 ACDSee是一款数字资产管理、图片管理编辑工具软件,提供良好的操作界面,简单人性化的操作方式,优质的快速图形解码方式,支持丰富的RAW格式,强大的图形文件管理功能等。 准备工作 1、提前准备好 ACDSee 2024 安装…

第20届纪念款-牛客周赛 Round 20 B.C简单构造

B 答案要么是0 要么是1 所以你全部填0或者要么填1然后算就好了 #include<bits/stdc.h> using namespace std; using ll long long; const int N 1e510; int n;void solve() {//全0 全1&#xff1f;string str;cin>>str;n str.size();string str1 str;int ans…

阿蒂亚(M.F.Atiyah)与黎曼猜想

&#xff08;注意&#xff1a;黑板上写的数不止20位数&#xff09; 德国柏林时间2018年9月24日上午9点45分&#xff0c;菲尔兹奖与阿贝尔奖双料得主、英国皇家学会院士迈克尔阿蒂亚爵士在德国海德堡举行的海德堡奖诺贝尔奖获得者论坛上&#xff0c;讲述了他对黎曼猜想的证明。…

设计模式⑩ :用类来实现

文章目录 一、前言二、Command 模式1. 介绍2.应用3. 总结 三、Interpreter 模式1. 介绍2. 应用3. 总结 参考文章 一、前言 有时候不想动脑子&#xff0c;就懒得看源码又不像浪费时间所以会看看书&#xff0c;但是又记不住&#xff0c;所以决定开始写"抄书"系列。本系…

springboot mongodb简单教程

&#xff08;1&#xff09;依赖 compile(org.springframework.boot:spring-boot-starter-data-mongodb) &#xff08;2&#xff09;application.properties配置文件 spring.data.mongodb.host127.0.0.1 spring.data.mongodb.port27017 spring.data.mongodb.databasetest &a…

docker由浅入深

一、什么是docker docker 顾名思义就是轮船的意思&#xff0c;轮船我们知道是通过集装箱运载货物的东西&#xff0c;那么docker其实也是类似的东西&#xff0c;只是装载的是虚拟的运行程序罢了。其中集装箱在docker里面被称为container&#xff08;后面以容器称之&#xff09;…

Vue2中Cesium加载离线地图

一、创建地图时添加 let imageryLayer2 = Cesium.ImageryLayer.fromProviderAsync(new Cesium.UrlTemplateImageryProvider({url:"data/Assets/Textures/NaturalEarthII" + "/{z}/{x}/{reverseY}.jpg",tilingScheme: new Cesium.GeographicTilingScheme(),…

软件安全测试的重要性简析,专业安全测试报告如何申请?

在当今数字化时代&#xff0c;软件在我们的日常生活中扮演着至关重要的角色&#xff0c;但也带来了各种潜在的安全威胁。为了保障用户的信息安全和维护软件的可靠性&#xff0c;软件安全测试显得尤为重要。 软件安全测试是指通过一系列的方法和技术&#xff0c;对软件系统中的…

短视频矩阵系统软件/电脑pc企业版/手机端双开发~~源头

短视频矩阵系统软件开发的属性主要包含以下几个方面&#xff1a; 开发属性&#xff1a; 1. 功能属性&#xff1a;功能是短视频矩阵系统的核心属性&#xff0c;包括短视频的采集、编辑、发布、推广、互动等功能。此外&#xff0c;系统的个性化定制也是考虑的重要因素&#xff0…

Qt实现验证码相关功能

验证码的原理 验证码的原理基于人类视觉和计算机视觉的差异性。通过给用户显示一些难以被机器识别的图形或文字&#xff0c;让用户进行人机交互&#xff0c;确认自己的身份。这样可以防止机器大规模注册、机器暴力破解数据密码等危害&#xff0c;保护网站安全。 Qt实现验证码…

循环的乐章与爱情的旋律

循环的乐章与爱情的旋律 The Rhapsody of Loops and the Melody of Love 在一个阳光明媚的Java编程课上&#xff0c;男主角林浩然&#xff0c;一个热衷于代码逻辑和算法谜题的大二学生&#xff0c;正沉浸在他的Java世界里。而女主角杨凌芸&#xff0c;则是班级中出了名的“程序…

【图论】拓扑排序

昨天复习的知识点。 ​先复习一下 AOE网。 AOE网&#xff0c;简单来说就是工程的带权有向图&#xff0c;其中&#xff1a; 顶点&#xff1a;活动开始或者结束的事件边&#xff1a;活动边的权值&#xff1a;完成该活动所需的时间 在AOE网中&#xff0c;想要完成一项活动&…

认识思维之熵

经常有读者问我&#xff0c;说&#xff1a; 为什么向您请教一个问题&#xff0c;您总能很快指出在哪篇文章里面提到过&#xff0c;是因为您的记忆力特别好吗&#xff1f; 其实不是的。更重要的原因是&#xff1a;如果你经过系统训练&#xff0c;有意识地去获取知识的话&#x…

华为服务器RAID5

0、BIOS默认密码 TaiShan 100服务器BIOS系统的默认密码为 Huawei12#$ TaiShan 200服务器BIOS系统的默认密码为 Admin9000 1、服务器开机选择DEL,进行设置 2、选择设备管理器进入配置页面 3、选择AVAGO MegaRAID configuration utility 进入raid配置 4、选择 Main Menu 进入主…

在线curl命令转换工具 - KGtools

KGtools提供在线curl命令转换工具&#xff0c;帮助您将curl命令转换为Python代码或命令行格式&#xff0c;将复杂的curl命令转换为可直接在Python程序中使用的代码 https://www.kgtools.cn/convert/curl

使用Transformer 模型进行时间序列预测的Pytorch代码示例

时间序列预测是一个经久不衰的主题&#xff0c;受自然语言处理领域的成功启发&#xff0c;transformer模型也在时间序列预测有了很大的发展。本文可以作为学习使用Transformer 模型的时间序列预测的一个起点。 数据集 这里我们直接使用kaggle中的 Store Sales — Time Series …

Vue3的computed和watch

目录 1、computed 2、computed完整写法 3、watch 4、watch监听对象具体属性 5、watch 监听reactive数据 1、computed 基于现有的数据计算出新的数据 <script setup >import {ref,computed} from vue const numref(1) const doubleNumcomputed(()>{return num.val…