【Golang】Go基础之文件复制 | 九

236 阅读4分钟

「这是我参与2022首次更文挑战的第11天,活动详情查看:2022首次更文挑战

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

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

1. 方法一:io包下的Read()和Write()方法实现

我们可以通过io包下的Read()和Write()方法,边读边写,就能够实现文件的复制。这个方法是按块读取文件,块的大小也会影响到程序的性能。

func File1(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

}

2. 方法二:io包下的Copy()方法实现

我们也可以直接使用io包下的()方法。

示例代码如下:

func File2(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)中,不止提供了()方法,还有另外2个公开的方法:N(),Buffer()。

Copy(dst,src) 为复制src 全部到 dst 中。

N(dst,src,n) 为复制src 中 n 个字节到 dst。

Buffer(dst,src,buf)为指定一个buf缓存区,以这个大小完全复制。

他们的关系如下:

从图可以看出,无论是哪个方法最终都是由Buffer()这个私有方法实现的。

func Buffer(dst Writer, src Reader, buf []byte) (written int64, err error) {
    
    
    if wt, ok := src.(WriterTo); ok {
        return wt.WriteTo(dst)
    }
    
    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)
    }

这部分主要是实现了对和N的处理。通过上面的调用关系图,我们看出N在调用后,会把Reader转成LimiteReader。

区别是如果,直接建立一个缓存区默认大小为 32* 1024 的buf,如果是N 会先判断 要复制的字节数,如果小于默认大小,会创建一个等于要复制字节数的buf。

3. 方法三:ioutil包

第三种方法是使用ioutil包中的 ioutil.WriteFile()ioutil.ReadFile(),但由于使用一次性读取文件,再一次性写入文件的方式,所以该方法不适用于大文件,容易内存溢出。

示例代码:

func File3(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
}

4. 总结

最后,我们来测试一下这3种拷贝需要花费时间,拷贝的文件都是一样的一个mp4文件(400M),

代码:

func main() {
    
    
    

    srcFile :="/Users/ruby/Documents/pro/a/001_小程序入门.mp4"
    destFile:="001_小程序入门.mp4"
    total,err:=File1(srcFile,destFile)
    fmt.Println(err)
    fmt.Println(total)

}

第一种:io包下Read()和Write()直接读写:我们自己创建读取数据的切片的大小,直接影响性能。

localhost:l_file ruby$ time go run demo05_.go 
拷贝完毕。。

401386819

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

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

localhost:l_file ruby$ time go run demo05_.go 

401386819

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

第三种:ioutil包

localhost:l_file ruby$ time go run demo05_.go 

401386819

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

运行结果:

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