本文是 Go 系列的第三篇文章,我将介绍三种最流行的复制文件的方法。
本文将介绍展示如何使用 Go 编程语言 来复制文件。在 Go 中复制文件的方法有很多,我只介绍三种最常见的:使用 Go 库中的 io.Copy()
函数调用、一次读取输入文件并将其写入另一个文件,以及使用缓冲区一块块地复制文件。
方法一:使用 io.Copy() 第一种方法就是使用 Go 标准库的 io.Copy()
函数。你可以在 copy()
函数的代码中找到它的实现逻辑,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 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
将产生以下输出:
1 2 3 4 5 6 $ 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
这个方法已经非常简单了,不过它没有为开发者提供灵活性。这并不总是一件坏事,但是,有些时候,开发者可能会需要/想要告诉程序该如何读取文件。
方法二:使用 ioutil.WriteFile() 和 ioutil.ReadFile() 复制文件的第二种方法是使用 ioutil.ReadFile()
和 ioutil.WriteFile()
函数。第一个函数用于将整个文件的内容,一次性地读入到某个内存中的字节切片里;第二个函数则用于将字节切片的内容写入到一个磁盘文件中。
实现代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 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
,你会得到下面的输出:
1 2 3 4 5 $ 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()
返回的字节切片会很大。
方法三:使用 os.Read() 和 os.Write() 在 Go 中复制文件的第三种方法就是下面要介绍的 cp3.go
。它接受三个参数:输入文件名、输出文件名和缓冲区大小。
cp3.go
最重要的部分位于以下 for
循环中,你可以在 copy()
函数中找到它,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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
,你会得到下面的输出:
1 2 3 4 5 6 7 $ 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.go
、cp2.go
和 cp3.go
的性能对比:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 $ ls -l INPUT -rw-r $ time go run cp1.go INPUT /tmp/cp1 Copied 512000000 bytes!real 0 m0.980 suser 0 m0.219 s sys 0 m0.719 s $ time go run cp2.go INPUT /tmp/cp2real 0 m1.139 suser 0 m0.196 s sys 0 m0.654 s $ time go run cp3.go INPUT /tmp/cp3 1000000 Copying INPUT to /tmp/cp3real 0 m1.025 suser 0 m0.195 s sys 0 m0.486 s
我们可以看出,这三个程序的性能非常接近,这意味着 Go 标准库函数的实现非常聪明、经过了充分优化。
现在,让我们测试一下缓冲区大小对 cp3.go
的性能有什么影响吧!执行 cp3.go
,并分别指定缓冲区大小为 10、20 和 1000 字节,在一台运行很快的机器上复制 500MB 文件,得到的结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 $ ls -l INPUT -rw-r $ time go run cp3.go INPUT /tmp/buf10 10 Copying INPUT to /tmp/buf10real 6 m39.721 suser 1 m18.457 s sys 5 m19.186 s $ time go run cp3.go INPUT /tmp/buf20 20 Copying INPUT to /tmp/buf20real 3 m20.819 suser 0 m39.444 s sys 2 m40.380 s $ time go run cp3.go INPUT /tmp/buf1000 1000 Copying INPUT to /tmp/buf1000real 0 m4.916 suser 0 m1.001 s sys 0 m3.986 s
我们可以发现,缓冲区越大,cp3.go
运行得就越快,这或多或少是符合预期的。此外,使用小于 20 字节的缓冲区来复制大文件会非常缓慢,应该避免。
你可以在 GitHub 找到 cp1.go
、cp2.go
和 cp3.go
的 Go 代码。
如果你有任何问题或反馈,请在(原文)下方发表评论或在 Twitter 上与我(原作者)联系。
via: https://opensource.com/article/18/6/copying-files-go
作者:Mihalis Tsoukalos 选题:lkxed 译者:lkxed 校对:wxy
本文由 LCTT 原创编译,Linux中国 荣誉推出