在Golang的标准库中,io包提供的Copy函数是实现文件拷贝的常用工具,它支持任意实现了Reader和Writer接口的对象之间的数据拷贝,内部自带缓冲机制,不需要开发者手动处理读写循环,尤其适合大文件的复制场景,能有效减少系统调用次数,提升拷贝效率。

io.Copy的基本工作原理
io.Copy的函数签名如下:
func Copy(dst Writer, src Reader) (written int64, err error)
它的核心逻辑是从src中读取数据,然后写入到dst中,直到src返回EOF或者发生错误。内部默认会使用32KB的缓冲区来暂存数据,避免频繁的小数据量读写操作,这也是它比手动循环读写效率更高的原因。
基础文件拷贝实现
使用io.Copy实现普通文件拷贝的步骤很简单,只需要打开源文件和目标文件,然后调用io.Copy即可:
package main
import (
"io"
"os"
)
func main() {
// 打开源文件,只读模式
srcFile, err := os.Open("source.txt")
if err != nil {
panic(err)
}
defer srcFile.Close()
// 创建目标文件,写入模式,权限为0644
dstFile, err := os.Create("target.txt")
if err != nil {
panic(err)
}
defer dstFile.Close()
// 执行拷贝操作
written, err := io.Copy(dstFile, srcFile)
if err != nil {
panic(err)
}
println("拷贝完成,总字节数:", written)
}
大文件拷贝的优化方案
自定义缓冲区大小
默认的32KB缓冲区对于超大文件来说可能不是最优选择,我们可以通过实现带自定义缓冲区的拷贝逻辑来提升效率:
package main
import (
"io"
"os"
)
type bufferedCopier struct {
buf []byte
}
func (c *bufferedCopier) Copy(dst io.Writer, src io.Reader) (int64, error) {
return io.CopyBuffer(dst, src, c.buf)
}
func main() {
srcFile, err := os.Open("large_source.bin")
if err != nil {
panic(err)
}
defer srcFile.Close()
dstFile, err := os.Create("large_target.bin")
if err != nil {
panic(err)
}
defer dstFile.Close()
// 设置1MB的缓冲区,适合大文件拷贝
copier := &bufferedCopier{buf: make([]byte, 1024*1024)}
written, err := copier.Copy(dstFile, srcFile)
if err != nil {
panic(err)
}
println("大文件拷贝完成,总字节数:", written)
}
错误处理与进度监控
大文件拷贝耗时较长,我们可以添加进度监控和更完善的错误处理:
package main
import (
"fmt"
"io"
"os"
"time"
)
func copyWithProgress(dst io.Writer, src io.Reader, total int64) error {
buf := make([]byte, 1024*1024)
var written int64
start := time.Now()
for {
nr, er := src.Read(buf)
if nr > 0 {
nw, ew := dst.Write(buf[0:nr])
if ew != nil {
return ew
}
if nr != nw {
return io.ErrShortWrite
}
written += int64(nw)
// 打印进度
if total > 0 {
percent := float64(written) / float64(total) * 100
fmt.Printf("r拷贝进度:%.2f%%", percent)
}
}
if er != nil {
if er == io.EOF {
break
}
return er
}
}
elapsed := time.Since(start)
fmt.Printf("n拷贝完成,耗时:%v,平均速度:%.2f MB/sn", elapsed, float64(written)/1024/1024/elapsed.Seconds())
return nil
}
func main() {
srcFile, err := os.Open("large_source.bin")
if err != nil {
panic(err)
}
defer srcFile.Close()
// 获取源文件大小
srcStat, err := srcFile.Stat()
if err != nil {
panic(err)
}
totalSize := srcStat.Size()
dstFile, err := os.Create("large_target.bin")
if err != nil {
panic(err)
}
defer dstFile.Close()
if err := copyWithProgress(dstFile, srcFile, totalSize); err != nil {
panic(err)
}
}
注意事项
- 拷贝前需要确认源文件存在且有读取权限,目标文件路径的目录需要有写入权限
- 大文件拷贝时要及时关闭文件句柄,避免资源泄露
- 如果拷贝过程中发生错误,建议删除不完整的目标文件,避免产生脏数据
- 对于跨设备的文件拷贝,io.Copy依然适用,不需要额外修改逻辑