Linux中國

在 Go 中複製文件的三種方法

本文是 Go 系列的第三篇文章,我將介紹三種最流行的複製文件的方法。

本文將介紹展示如何使用 Go 編程語言複製文件。在 Go複製文件的方法有很多,我只介紹三種最常見的:使用 Go 庫中的 io.Copy() 函數調用、一次讀取輸入文件並將其寫入另一個文件,以及使用緩衝區一塊塊地複製文件。

方法一:使用 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

這個方法已經非常簡單了,不過它沒有為開發者提供靈活性。這並不總是一件壞事,但是,有些時候,開發者可能會需要/想要告訴程序該如何讀取文件。

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

複製文件的第二種方法是使用 ioutil.ReadFile()ioutil.WriteFile() 函數。第一個函數用於將整個文件的內容,一次性地讀入到某個內存中的位元組切片里;第二個函數則用於將位元組切片的內容寫入到一個磁碟文件中。

實現代碼如下:

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() 返回的位元組切片會很大。

方法三:使用 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 的性能有什麼影響吧!執行 cp3.go,並分別指定緩衝區大小為 10、20 和 1000 位元組,在一台運行很快的機器上複製 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 上與我(原作者)聯繫。

via: https://opensource.com/article/18/6/copying-files-go

作者:Mihalis Tsoukalos 選題:lkxed 譯者:lkxed 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出


本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive

對這篇文章感覺如何?

太棒了
1
不錯
0
愛死了
0
不太好
0
感覺很糟
0
雨落清風。心向陽

    You may also like

    Leave a reply

    您的郵箱地址不會被公開。 必填項已用 * 標註

    此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據

    More in:Linux中國