input禁止后怎么实现复制功能_GO语言基础进阶教程:文件复制

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

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

1cc9a7d331f67b1c77c590078e7f36b7.png

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

他们的关系如下:

d0add8bcc8b31eda313a120ae702b327.png

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

05ed198e37609a892943ffc337e2beda.png

代码:

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 
拷贝完毕。。
<nil>
401386819
​
real    0m7.911s
user    0m2.900s
sys     0m7.661s

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

localhost:l_file ruby$ time go run demo05_copy.go 
<nil>
401386819
​
real    0m1.594s
user    0m0.533s
sys     0m1.136s
​
​

第三种:ioutil包

localhost:l_file ruby$ time go run demo05_copy.go 
<nil>
401386819
​
real    0m1.515s
user    0m0.339s
sys     0m0.625s
​

运行结果:

8726b51b5e03cf80ccda7a5405b7ff54.png

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值