第004节:文件复制

                           

在io包中主要是操作流的一些方法,今天主要学习一下copy。就是把一个文件复制到另一个目录下。

它的原理就是通过程序,从源文件读取文件中的数据,在写出到目标文件里。

copyfile

一、方法一: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缓存区,以这个大小完全复制。

他们的关系如下:

20190316084535903

从图可以看出,无论是哪个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),
WX20190702-124039

代码:

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 
拷贝完毕。。

401386819

real    0m7.911s
user    0m2.900s
sys     0m7.661s

第二种:io包下Copy()方法:

localhost:l_file ruby$ time go run demo05_copy.go 

401386819

real    0m1.594s
user    0m0.533s
sys     0m1.136s

第三种:ioutil包

localhost:l_file ruby$ time go run demo05_copy.go 

401386819

real    0m1.515s
user    0m0.339s
sys     0m0.625s

运行结果:

WX20190702-124719

这3种方式,在性能上,不管是还是io.Copy()还是ioutil包,性能都是还不错的。

原创文章,Golang中国出品,文章对应源码下载:https://www.qfgolang.com/?page_id=1973

发表评论

电子邮件地址不会被公开。 必填项已用*标注

联系我们

学习交流群:点击这里给我发消息

QR code