Go 语言中复制文件的 3 种方法

在本系列关于 Go 编程语言的第三篇文章中,了解复制文件的三种最流行方法。
380 位读者喜欢这篇文章。
cats

Opensource.com

本文是 Mihalis Tsoukalos 撰写的 Go 系列文章的一部分。阅读第 1 部分:《在 Go 中创建随机、安全的密码》,以及第 2 部分:《在 Go 中构建并发 TCP 服务器》。

本文将向您展示如何在 Go 编程语言中复制文件。尽管在 Go 中复制文件的方法不止三种,但本文将介绍三种最常见的方法:使用 Go 库中的 io.Copy() 函数调用;一次性读取输入文件并将其写入另一个文件;以及使用缓冲区以小块复制文件。

方法 1:使用 io.Copy()

该实用程序的第一个版本将使用标准 Go 库的 io.Copy() 函数。实用程序的逻辑可以在 copy() 函数的实现中找到,如下所示

func copy(src, dst string) (int64, error) {
        sourceFileStat, err := os.Stat(src)
        if err != nil {
                return 0, err
        }

        if !sourceFileStat.Mode().IsRegular() {
                return 0, fmt.Errorf("%s is not a regular file", src)
        }

        source, err := os.Open(src)
        if err != nil {
                return 0, err
        }
        defer source.Close()

        destination, err := os.Create(dst)
        if err != nil {
                return 0, err
        }
        defer destination.Close()
        nBytes, err := io.Copy(destination, source)
        return nBytes, err
}

除了测试要复制的文件是否存在 (os.Stat(src)) 以及是否为常规文件 (sourceFileStat.Mode().IsRegular()) 以便您可以打开它进行读取之外,所有工作都由 io.Copy(destination, source) 语句完成。io.Copy() 函数返回复制的字节数以及复制过程中发生的第一个错误消息。在 Go 中,如果没有错误消息,错误变量的值将为 nil

您可以在 io 包文档页面了解更多关于 io.Copy() 函数的信息。

执行 cp1.go 将生成以下类型的输出

$ go run cp1.go
Please provide two command line arguments!
$ go run cp1.go fileCP.txt /tmp/fileCPCOPY
Copied 3826 bytes!
$ diff fileCP.txt /tmp/fileCPCOPY

这种技术尽可能简单,但没有给开发者带来灵活性,这并不总是一件坏事。但是,有时开发者需要或想要决定他们想要如何读取文件。

方法 2:使用 ioutil.WriteFile() 和 ioutil.ReadFile()

复制文件的第二种方法是使用 ioutil.ReadFile()ioutil.WriteFile() 函数。第一个函数将整个文件的内容读取到字节切片中,第二个函数将字节切片的内容写入文件。

实用程序的逻辑可以在以下 Go 代码中找到

        input, err := ioutil.ReadFile(sourceFile)
        if err != nil {
                fmt.Println(err)
                return
        }

        err = ioutil.WriteFile(destinationFile, input, 0644)
        if err != nil {
                fmt.Println("Error creating", destinationFile)
                fmt.Println(err)
                return
        }

除了两个 if 代码块(它们是 Go 工作方式的一部分)之外,您可以看到程序的功能在 ioutil.ReadFile()ioutil.WriteFile() 语句中。

执行 cp2.go 将生成以下类型的输出

$ go run cp2.go
Please provide two command line arguments!
$ go run cp2.go fileCP.txt /tmp/copyFileCP
$ diff fileCP.txt /tmp/copyFileCP

请注意,尽管这种技术可以复制文件,但当您想要复制大型文件时,它可能效率不高,因为 ioutil.ReadFile() 返回的字节切片也会很大。

方法 3:使用 os.Read() 和 os.Write()

在 Go 中复制文件的第三种方法是使用 cp3.go 实用程序,该实用程序将在本节中开发。它接受三个参数:输入文件的文件名、输出文件的文件名和缓冲区的大小。

cp3.go 最重要的部分位于以下 for 循环中,该循环可以在 copy() 函数中找到:

        buf := make([]byte, BUFFERSIZE)
        for {
                n, err := source.Read(buf)
                if err != nil && err != io.EOF {
                        return err
                }
                if n == 0 {
                        break
                }

                if _, err := destination.Write(buf[:n]); err != nil {
                        return err
                }
        }

这种技术使用 os.Read() 将输入文件的小部分读取到名为 buf 的缓冲区中,并使用 os.Write() 将该缓冲区的内容写入文件。当读取时出现错误或到达文件末尾 (io.EOF) 时,复制过程停止。

执行 cp3.go 将生成以下类型的输出

$ go run cp3.go
usage: cp3 source destination BUFFERSIZE
$ go run cp3.go fileCP.txt /tmp/buf10 10
Copying fileCP.txt to /tmp/buf10
$ go run cp3.go fileCP.txt /tmp/buf20 20
Copying fileCP.txt to /tmp/buf20

您将看到,缓冲区的大小极大地影响了 cp3.go 的性能。

进行一些基准测试

本文的最后一部分将尝试比较这三个程序以及 cp3.go 在使用 time(1) 命令行实用程序时不同缓冲区大小的性能。

以下输出显示了复制 500MB 文件时 cp1.gocp2.gocp3.go 的性能

$ ls -l INPUT
-rw-r--r--  1 mtsouk  staff  512000000 Jun  5 09:39 INPUT
$ time go run cp1.go INPUT /tmp/cp1
Copied 512000000 bytes!

real    0m0.980s
user    0m0.219s
sys     0m0.719s
$ time go run cp2.go INPUT /tmp/cp2

real    0m1.139s
user    0m0.196s
sys     0m0.654s
$ time go run cp3.go INPUT /tmp/cp3 1000000
Copying INPUT to /tmp/cp3

real    0m1.025s
user    0m0.195s
sys     0m0.486s

输出表明,所有三个实用程序的性能非常相似,这意味着标准 Go 库的函数非常巧妙且经过优化。

现在,让我们测试缓冲区大小如何影响 cp3.go 的性能。在相当快的机器上,使用缓冲区大小为 10、20 和 1,000 字节执行 cp3.go 以复制 500MB 文件将生成以下结果

$ ls -l INPUT
-rw-r--r--  1 mtsouk  staff  512000000 Jun  5 09:39 INPUT
$ time go run cp3.go INPUT /tmp/buf10 10
Copying INPUT to /tmp/buf10

real    6m39.721s
user    1m18.457s
sys         5m19.186s
$ time go run cp3.go INPUT /tmp/buf20 20
Copying INPUT to /tmp/buf20

real    3m20.819s
user    0m39.444s
sys         2m40.380s
$ time go run cp3.go INPUT /tmp/buf1000 1000
Copying INPUT to /tmp/buf1000

real    0m4.916s
user    0m1.001s
sys     0m3.986s

生成的输出表明,缓冲区越大,cp3.go 实用程序的性能越快,这或多或少是预期的。此外,对于复制大型文件,使用小于 20 字节的缓冲区大小是一个非常缓慢的过程,应避免使用。

您可以在 GitHub 上找到 cp1.gocp2.gocp3.go 的 Go 代码。

如果您有任何问题或反馈,请在下方留言或在 Twitter 上与我联系。

User profile image.
Mihalis Tsoukalos 是一位技术作家、UNIX 管理员和开发者、DBA 和数学家。他是《Go 系统编程》和《精通 Go》的作者。您可以通过 http://www.mtsoukalos.eu/ 和 https://twitter.com/mactsouk 联系他。

评论已关闭。

Creative Commons License本作品根据 Creative Commons Attribution-Share Alike 4.0 国际许可协议获得许可。
© . All rights reserved.