Golang三種方式拷貝文件

Golang三種方式拷貝文件

本文介紹三種典型拷貝文件方式,同時比較三種方法的效率,讓我們瞭解什麼場景下選擇合適的方法。

1. 拷貝的文件三種方法

三種典型的方法分別爲Go標準庫提供的io.Copy(),第二種方法是利用ioutil.ReadFile() 和 ioutil.WriteFile() ,最後是使用os.Read() 和 os.Write()方法。

1.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
}

首先判斷源文件是否存在(oa.Stat()方法),然後判斷是否爲常規文件(IsRegular()方法),確保文件可以被打開,核心的語句是io.Copy(destination, source),其返回拷貝的字節數和發生錯誤的信息。如果沒有錯誤則返回nil。io.Copy()函數更多內容可參考文檔或其源碼。

下面定義main函數進行調用:

func main() {
	if len(os.Args) != 3 {
		fmt.Println("Please provide two command line arguments!")
		return
	}

	sourceFile := os.Args[1]
	destinationFile := os.Args[2]

	nBytes, err := copy(sourceFile, destinationFile)
	if err != nil {
		fmt.Printf("The copy operation failed %q\n", err)
	} else {
		fmt.Printf("Copied %d bytes!\n", nBytes)
	}
}

這種方法很簡單但對開發者來說不靈活,雖不是壞事,但有時需要靈活讀取文件或寫文件。

1.2 ioutil.ReadFile() 和 ioutil.WriteFile()

第二種方法使用ioutil.ReadFile() 和 ioutil.WriteFile() 。第一個函數把整個文件讀到字節類型切片中,第二個函數負責寫至文件中。

我們定義copy2()函數:

    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
    }

當然也需要文件名判斷部分,讀者可參考上節內容,主要功能就是讀和寫部分。
這種方法也實現了拷貝功能,但在拷貝大文件時效率不高,因爲讀取大文件暫用內存也大。

1.3 os.Read() 和 os.Write()

第三種方法使用os.Read() 和 os.Write(),實現內容公共部分都一致,但多了一個參數,即緩衝大小。核心代碼在for循環中,請看代碼:

    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()方法每次讀取文件的一小部分至緩衝區,os.Write()方法寫緩衝區至文件。在讀過程有錯誤或讀到文件結尾(io.EOF)拷貝過程停止.

完整代碼爲:

package main

import (
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strconv"
)

var BUFFERSIZE int64

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

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

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

	_, err = os.Stat(dst)
	if err == nil {
		return fmt.Errorf("File %s already exists.", dst)
	}

	destination, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer destination.Close()

	if err != nil {
		panic(err)
	}

	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
		}
	}
	return err
}

func main() {
	if len(os.Args) != 4 {
		fmt.Printf("usage: %s source destination BUFFERSIZE\n", filepath.Base(os.Args[0]))
		return
	}

	source := os.Args[1]
	destination := os.Args[2]
	BUFFERSIZE, err := strconv.ParseInt(os.Args[3], 10, 64)
	if err != nil {
		fmt.Printf("Invalid buffer size: %q\n", err)
		return
	}

	fmt.Printf("Copying %s to %s\n", source, destination)
	err = copy(source, destination, BUFFERSIZE)
	if err != nil {
		fmt.Printf("File copying failed: %q\n", err)
	}
}

2. 測試

下面我們利用linux的time命令實現簡單基準測試,首先對三種方法進行基準測試,然後第三種方法採用不同的緩衝區大小參數進行測試。

下面使用三種方法測試500M文件拷貝,對比三種性能:

$ 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

我們看到三者差別不大,說明標準庫提供的方法是經過優化的。下面測試第三種方法不同緩衝區大小參數的性能,10、20和1000字節三種情況分別拷貝500M文件:

$ 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

輸出結果顯示較大的緩存區考覈性能更好。同時使用20字節以下拷貝速度非常慢。

3. 總結

本文討論了三種拷貝方法,並通過time命令進行基準測試比對性能。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章