在io包中主要是操作流的一些方法,今天主要学习一下copy。就是把一个文件复制到另一个目录下。
它的原理就是通过程序,从源文件读取文件中的数据,在写出到目标文件里。
一、方法一:io包下的Read()和Write()方法实现
我们可以通过io包下的Read()和Write()方法,边读边写,就能够实现文件的复制。这个方法是按块读取文件,块的大小也会影响到程序的性能。
}func copyFile1(srcFile,destFile string)(int,error){ file1,err:=os.Open(srcFile) if err != nil{ return 0,err } file2,err:=os.OpenFile(destFile,os.O_WRonLY|os.O_CREATE,os.ModePerm) if err !=nil{ return 0,err } defer file1.Close() defer file2.Close() //拷贝数据 bs := make([]byte,1024,1024) n :=-1//读取的数据量 total := 0 for { n,err = file1.Read(bs) if err == io.EOF || n == 0{ fmt.Println("拷贝完毕。。") break }else if err !=nil{ fmt.Println("报错了。。。") return total,err } total += n file2.Write(bs[:n]) } return total,nil}二、方法二:io包下的Copy()方法实现
我们也可以直接使用io包下的Copy()方法。
示例代码如下:
func copyFile2(srcFile, destFile string)(int64,error){ file1,err:=os.Open(srcFile) if err != nil{ return 0,err } file2,err:=os.OpenFile(destFile,os.O_WRonLY|os.O_CREATE,os.ModePerm) if err !=nil{ return 0,err } defer file1.Close() defer file2.Close() return io.Copy(file2,file1)}扩展内容:
在io包(golang 版本 1.12)中,不止提供了Copy()方法,还有另外2个公开的copy方法:CopyN(),CopyBuffer()。
Copy(dst,src) 为复制src 全部到 dst 中。CopyN(dst,src,n) 为复制src 中 n 个字节到 dst。CopyBuffer(dst,src,buf)为指定一个buf缓存区,以这个大小完全复制。
他们的关系如下:
从图可以看出,无论是哪个copy方法最终都是由copyBuffer()这个私有方法实现的。
func copyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) { // If the reader has a WriteTo method, use it to do the copy. // Avoids an allocation and a copy. if wt, ok := src.(WriterTo); ok { return wt.WriteTo(dst) } // Similarly, if the writer has a ReadFrom method, use it to do the copy. if rt, ok := dst.(ReaderFrom); ok { return rt.ReadFrom(src) } if buf == nil { size := 32 * 1024 if l, ok := src.(*LimitedReader); ok && int64(size) > l.N { if l.N < 1 { size = 1 } else { size = int(l.N) } } buf = make([]byte, size) } for { nr, er := src.Read(buf) if nr > 0 { nw, ew := dst.Write(buf[0:nr]) if nw > 0 { written += int64(nw) } if ew != nil { err = ew break } if nr != nw { err = ErrShortWrite break } } if er != nil { if er != EOF { err = er } break } } return written, err}从这部分代码可以看出,复制主要分为3种。
1.如果被复制的Reader(src)会尝试能否断言成writerTo,如果可以则直接调用下面的writerTo方法
2.如果 Writer(dst) 会尝试能否断言成ReadFrom ,如果可以则直接调用下面的readfrom方法
3.如果都木有实现,则调用底层read实现复制。
其中,有这么一段代码:
if buf == nil { size := 32 * 1024 if l, ok := src.(*LimitedReader); ok && int64(size) > l.N { if l.N < 1 { size = 1 } else { size = int(l.N) } } buf = make([]byte, size) }这部分主要是实现了对Copy和CopyN的处理。通过上面的调用关系图,我们看出CopyN在调用后,会把Reader转成LimiteReader。
区别是如果Copy,直接建立一个缓存区默认大小为 32* 1024 的buf,如果是CopyN 会先判断 要复制的字节数,如果小于默认大小,会创建一个等于要复制字节数的buf。
三、方法三:ioutil包
第三种方法是使用ioutil包中的 ioutil.WriteFile()和 ioutil.ReadFile(),但由于使用一次性读取文件,再一次性写入文件的方式,所以该方法不适用于大文件,容易内存溢出。
示例代码:
func copyFile3(srcFile, destFile string)(int,error){ input, err := ioutil.ReadFile(srcFile) if err != nil { fmt.Println(err) return 0,err } err = ioutil.WriteFile(destFile, input, 0644) if err != nil { fmt.Println("操作失败:", destFile) fmt.Println(err) return 0,err } return len(input),nil}四、总结
最后,我们来测试一下这3种拷贝需要花费时间,拷贝的文件都是一样的一个mp4文件(400M),
代码:
func main() { //srcFile := "/home/ruby/文档/pro/aa.txt" //destFile := "/home/ruby/文档/aa.txt" srcFile :="/Users/ruby/documents/pro/a/001_小程序入门.mp4" destFile:="001_小程序入门.mp4" total,err:=copyFile1(srcFile,destFile) fmt.Println(err) fmt.Println(total)}第一种:io包下Read()和Write()直接读写:我们自己创建读取数据的切片的大小,直接影响性能。
localhost:l_file ruby$ time go run demo05_copy.go 拷贝完毕。。401386819real 0m7.911suser 0m2.900ssys 0m7.661s
第二种:io包下Copy()方法:
localhost:l_file ruby$ time go run demo05_copy.go401386819real 0m1.594suser 0m0.533ssys 0m1.136s
第三种:ioutil包
localhost:l_file ruby$ time go run demo05_copy.go401386819real 0m1.515suser 0m0.339ssys 0m0.625s
运行结果:
这3种方式,在性能上,不管是还是io.Copy()还是ioutil包,性能都是还不错的。





