一文了解 io.Copy 函数
1. 引言
io.Copy
函数是一个非常好用的函数,能够非常方便得将数据进行拷贝。本文我们将从io.Copy
函数的基本定义出发,讲述其基本使用和实现原理,以及一些注意事项,基于此完成对io.Copy
函数的介绍。
2. 基本说明
2.1 基本定义
Copy
函数用于将数据从源(io.Reader
)复制到目标(io.Writer
)。它会持续复制直到源中的数据全部读取完毕或发生错误,并返回复制的字节数和可能的错误。函数定义如下:
func Copy(dst io.Writer, src io.Reader) (written int64, err error)
其中dst
为目标写入器,用于接收源数据;src
则是源读取器,用于提供数据。
2.2 使用示例
下面提供一个使用 io.Copy
实现数据拷贝的代码示例,比便更好得理解和使用Copy
函数,代码示例如下:
package main
import (
"fmt"
"io"
"os"
)
func main() {
fmt.Print("请输入一个字符串:")
src := readString()
// 通过io.Copy 函数能够将 src 的全部数据 拷贝到 控制台上输出
written, err := io.Copy(os.Stdout, src)
if err != nil {
fmt.Println("复制过程中发生错误:", err)
return
}
fmt.Printf("\n成功复制了 %d 个字节。\n", written)
}
func readString() io.Reader {
buffer := make([]byte, 1024)
n, _ := os.Stdin.Read(buffer)
// 如果实际读取的字节数少于切片长度,则截取切片
if n < len(buffer) {
buffer = buffer[:n]
}
return strings.NewReader(string(buffer))
}
在这个例子中,我们首先使用readString
函数从标准输入中读取字符串,然后使用strings.NewReader
将其包装为io.Reader
返回。
然后,我们调用io.Copy
函数,将读取到数据全部复制到标准输出(os.Stdout
)。最后,我们打印复制的字节数。可以运行这个程序并在终端输入一个字符串,通过Copy
函数,程序最终会将字符串打印到终端上。
3. 实现原理
在了解了io.Copy
函数的基本定义和使用后,这里我们来对 io.Copy
函数的实现来进行基本的说明,加深对 io.Copy
函数的理解。
io.Copy
基本实现原理如下,首先创建一个缓冲区,用于暂存从源Reader读取到的数据。然后进入一个循环,每次循环从源Reader读取数据,然后存储到之前创建的缓冲区,之后再写入到目标Writer中。不断重复这个过程,直到源Reader返回EOF,此时代表数据已经全部读取完成,io.Copy
也完成了从源Reader往目标Writer拷贝全部数据的工作。
在这个过程中,如果往目标Writer
写入数据过程中发生错误,亦或者从源Reader
读取数据发生错误,此时io.Copy
函数将会中断,然后返回对应的错误。下面我们来看io.Copy
的实现:
func Copy(dst Writer, src Reader) (written int64, err error) {
// Copy 函数 调用了 copyBuffer 函数来实现
return copyBuffer(dst, src, nil)
}
func copyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) {
// 如果 源Reader 实现了 WriterTo 接口,直接调用该方法 将数据写入到 目标Writer 当中
if wt, ok := src.(WriterTo); ok {
return wt.WriteTo(dst)
}
// 同理,如果 目标Writer 实现了 ReaderFrom 接口,直接调用ReadFrom方法
if rt, ok := dst.(ReaderFrom); ok {
return rt.ReadFrom(src)
}
// 如果没有传入缓冲区,此时默认 创建一个 缓冲区
if buf == nil {
// 默认缓冲区 大小为 32kb
size := 32 * 1024
// 如果源Reader 为LimitedReader, 此时比较 可读数据数 和 默认缓冲区,取较小那个
if l, ok := src.(*LimitedReader); ok && int64(size) > l.N {
if l.N < 1 {
size = 1
} else {
size = int(l.N)
}
}
buf = make([]byte, size)
}
for {
// 调用Read方法 读取数据
nr, er := src.Read(buf)
if nr > 0 {
// 将数据写入到 目标Writer 当中
nw, ew := dst.Write(buf[0:nr])
// 判断写入是否 出现了 错误
if nw < 0 || nr < nw {
nw = 0
if ew == nil {
ew = errInvalidWrite
}
}
// 累加 总写入数据
written += int64(nw)
if ew != nil {
err = ew
break
}
// 写入字节数 小于 读取字节数,此时报错
if nr != nw {
err = ErrShortWrite
break
}
}
if er != nil {
if er != EOF {
err = er
}
break
}
}
return written, err
}
从上述基本原理和代码实现来看,io.Copy
函数的实现还是非常简单的,就是申请一个缓冲区,然后从源Reader读取一些数据放到缓冲区中,然后再将缓冲区的数据写入到 目标Writer, 如此往复,直到数据全部读取完成。
4. 注意事项
4.1 注意关闭源Reader和目标Writer
在使用io.Copy
进行数据拷贝时,需要指定源Reader 和 目标Writer,当io.Copy
完成数据拷贝工作后,我们需要调用Close
方法关闭 源Reader 和 目标Writer。如果没有适时关闭资源,可能会导致一些不可预料情况的出现。
下面展示一个使用 io.Copy
进行文件复制的代码示例,同时简单说明不适时关闭资源可能导致的问题:
package main
import (
"fmt"
"io"
"os"
)
func main() {
sourceFile := "source.txt"
destinationFile := "destination.txt"
// 打开源文件
src, err := os.Open(sourceFile)
if err != nil {
fmt.Println("无法打开源文件:", err)
return
}
// 调用Close方法
defer src.Close()
// 创建目标文件
dst, err := os.Create(destinationFile)
if err != nil {
fmt.Println("无法创建目标文件:", err)
return
}
// 调用Close 方法
defer dst.Close()
// 执行文件复制
_, err = io.Copy(dst, src)
if err != nil {
fmt.Println("复制文件出错:", err)
return
}
fmt.Println("文件复制成功!")
}
使用 io.Copy
函数将源文件的内容复制到目标文件中。在结束代码之前,我们需要适时地关闭源文件和目标文件。以上面使用io.Copy
实现文件复制功能为例,如果我们没有适时关闭资源,首先是可能会导致文件句柄泄漏,数据不完整等一系列问题的出现。
因此我们在io.Copy
函数之后,需要在适当的地方调用Close
关闭系统资源。
4.2 考虑性能问题
io.Copy
函数默认使用一个32KB大小的缓冲区来复制数据,如果我们处理的是大型文件,亦或者是高性能要求的场景,此时是可以考虑直接使用io.CopyBuffer
函数,自定义缓冲区大小,以优化复制性能。而io.Copy
和io.CopyBuffer
底层其实都是调用io.copyBuffer
函数的,二者底层实现其实没有太大的区别。
下面通过一个基准测试,展示不同缓冲区大小对数据拷贝性能的影响:
func BenchmarkCopyWithBufferSize(b *testing.B) {
// 本地运行时, 文件大小为 100 M
filePath := "largefile.txt"
bufferSizes := []int{32 * 1024, 64 * 1024, 128 * 1024} // 不同的缓冲区大小
for _, bufferSize := range bufferSizes {
b.Run(fmt.Sprintf("BufferSize-%d", bufferSize), func(b *testing.B) {
for n := 0; n < b.N; n++ {
src, _ := os.Open(filePath)
dst, _ := os.Create("destination.txt")
buffer := make([]byte, bufferSize)
_, _ = io.CopyBuffer(dst, src, buffer)
_ = src.Close()
_ = dst.Close()
_ = os.Remove("destination.txt")
}
})
}
}
这里我们定义的缓冲区大小分别是32KB, 64KB和128KB,然后使用该缓冲区来拷贝数据。下面我们看基准测试的结果:
BenchmarkCopyWithBufferSize/BufferSize-32768-4 12 116494592 ns/op
BenchmarkCopyWithBufferSize/BufferSize-65536-4 10 110496584 ns/op
BenchmarkCopyWithBufferSize/BufferSize-131072-4 12 87667712 ns/op
从这里看来,32KB大小的缓冲区拷贝一个100M的文件,需要116494592 ns/op
, 而128KB大小的缓冲区拷贝一个100M的文件,需要87667712 ns/op
。不同缓冲区的大小,确实是会对拷贝的性能有一定的影响。
在实际使用中,根据文件大小、系统资源和性能需求,可以根据需求进行缓冲区大小的调整。较小的文件通常可以直接使用io.Copy
函数默认的 32KB 缓冲区,而较大的文件可能需要更大的缓冲区来提高性能。通过合理选择缓冲区大小,可以获得更高效的文件复制操作。
5. 总结
io.Copy
函数是Go语言标准库提供的一个工具函数,能够将数据从源Reader复制到目标Writer。 我们先从io.Copy
函数的基本定义出发,之后通过一个简单的示例,展示如何使用io.Copy
函数实现数据拷贝。
接着我们讲述了io.Copy
函数的实现原理,其实就是定义了一个缓冲区,将源Reader数据写入到缓冲区中,然后再将缓冲区的数据写入到目标Writer,不断重复这个过程,实现了数据的拷贝。
在注意事项方面,则强调了及时关闭源Reader和目标Writer的重要性。以及用户在使用时,需要考虑io.Copy
函数的性能是否能够满足要求,之后通过基准测试展示了不同缓冲区大小可能带来的性能差距。
基于此,完成了对io.Copy
函数的介绍,希望对你有所帮助。
一文了解 io.Copy 函数的更多相关文章
- php不使用copy()函数复制文件的方法
本文实例讲述了php不使用copy()函数复制文件的方法.分享给大家供大家参考.具体如下:下面的代码不使用php内置的copy函数,直接通过文件读取写入的操作方式复制文件 <?php funct ...
- 利用copy函数简单快速输出/保存vector向量容器中的数据
如果要输出vector中的数据我们可以通过循环语句输出,更加简便的方法是利用copy函数直接输出,例子: #include "stdafx.h" #include <iost ...
- 使用copy函数完成数据库迁移
最近在该一个迁移工具的迁移方式,从ora8迁移到postgresql使用原来的插入迁移速度太慢了,老板说让使用缓存迁移,即使用postgresql的copy函数,因此去pg官网查阅了相关资料,我们需要 ...
- C++ STL copy函数效率分析
在C++编程中,经常会配到数据的拷贝,如数组之间元素的拷贝,一般的人可能都会用for循环逐个元素进行拷贝,在数据量不大的情况下还可以,如果数据量比较大,那么效率会比较地下.而STL中就提供了一个专门用 ...
- 使用korofileheader插件vs code添加文件头注释和函数注释
korofileheadervs code添加文件头注释和函数注释1.extensions搜索fileheader,安装koroFileHeader2.设置:edit=>perference=& ...
- 使用copy函数输出容器中的内容
container<type> c; 输出语句为:copy(c.begin(), c.end(), ostream_iterator<type>(cout, " &q ...
- PHP通过copy()函数来复制一个文件
PHP通过copy()函数来复制一个文件.用法如下: bool copy(string $source, string $dest) 其中$source是源文件的路径,$dest是目的文件的路径.函数 ...
- C++ - 使用copy函数打印容器(container)元素
使用copy函数打印容器(container)元素 本文地址: http://blog.csdn.net/caroline_wendy C++能够使用copy函数输出容器(container)中的元素 ...
- openssl之BIO系列之6---BIO的IO操作函数
BIO的IO操作函数 ---依据openssl doc/crypto/bio/bio_read.pod翻译和自己的理解写成 (作者:DragonKing Mail:wzhah ...
- 1.Go语言copy函数、sort排序、双向链表、list操作和双向循环链表
1.1.copy函数 通过copy函数可以把一个切片内容复制到另一个切片中 (1)把长切片拷贝到短切片中 package main import "fmt" func main() ...
随机推荐
- Sqlmap注入dvwa平台low级别
工具介绍:sqlmap是一款开源的软件 SQL注入攻击是黑客对数据库进行攻击的常用手段之一.随着B/S模式应用开发的发展,使用这种模式编写应用程序的程序员也越来越多.但是由于程序员的水平及经验也参差不 ...
- Java 生成海报
最近项目有个功能是生成海报 一个背景图片,一个二维码图片 将两个图片合并成一个图片. 写了一个工具类,需要的朋友自取. 1 @Component 2 public class PictureUtil ...
- MySQL主从复制原理剖析与应用实践
vivo 互联网服务器团队- Shang Yongxing MySQL Replication(主从复制)是指数据变化可以从一个MySQL Server被复制到另一个或多个MySQL Server上, ...
- 【书籍阅读】【Spring实战】二 装配Bean
Spring配置的可选方案 ①自动化装配Bean:@Component ②Java代码装配Bean:@Bean ③XML装配Bean 自动化装配Bean Spring从两个角度实现对 @Compone ...
- 谈一谈Python中的装饰器
1.装饰器基础介绍 1.1 何为Python中的装饰器? Python中装饰器的定义以及用途: 装饰器是一种特殊的函数,它可以接受一个函数作为参数,并返回一个新的函数.装饰器可以用来修改或增强函数的行 ...
- APISIX Ingress 如何使用 Cert Manager 管理证书
Apache APISIX Ingress Controller 是一款以 Apache APISIX 作为数据面的 Kubernetes Ingress Controller 开源工具,目前已经更新 ...
- 【python】使用爬虫爬取动漫之家漫画全部更新信息
本篇仅在于交流学习 网站名称为: https://manhua.dmzj.com/ 1.首先将相应的库导入: import requests from lxml import etree 2.确定漫画 ...
- 2022-02-17:寻找最近的回文数。 给定一个表示整数的字符串 n ,返回与它最近的回文整数(不包括自身)。如果不止一个,返回较小的那个。 “最近的”定义为两个整数差的绝对值最小。 示例 1: 输
2022-02-17:寻找最近的回文数. 给定一个表示整数的字符串 n ,返回与它最近的回文整数(不包括自身).如果不止一个,返回较小的那个. "最近的"定义为两个整数差的绝对值最 ...
- 2021-06-28:最接近目标值的子序列和。给你一个整数数组 nums 和一个目标值 goal 。你需要从 nums 中选出一个子序列,使子序列元素总和最接近 goal 。也就是说,如果子序列元素和
2021-06-28:最接近目标值的子序列和.给你一个整数数组 nums 和一个目标值 goal .你需要从 nums 中选出一个子序列,使子序列元素总和最接近 goal .也就是说,如果子序列元素和 ...
- vue全家桶进阶之路2:JavaScript
JavaScript(简称"JS")是当前最流行.应用最广泛的客户端脚本语言,用来在网页中添加一些动态效果与交互功能,在 Web 开发领域有着举足轻重的地位.JavaScript ...