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命令進行基準測試比對性能。