扫码订阅《 Go语言基础进阶》或入驻星球,即可阅读文章!

GOLANG ROADMAP

阅读模式

  • 沉浸
  • 自动
  • 日常
首页
Go友会
  • 城市
  • 校园
Go学院
  • Go小课
  • Go小考
  • Go实战
  • 精品课
Go求职
  • 求职辅导🔥
  • Offer收割社群
  • 企业题库
  • 面试宝典
Go宝典
  • 在线宝典
  • B站精选
  • 推荐图书
  • 每日博文
Go仓库
实验区
  • Go周边
  • Go下载
  • Go月刊
消息
更多
  • 用户中心

    • 我的信息
    • 推广返利
  • 玩转星球

    • 星球介绍
    • 角色体系
    • 星主权益
  • 支持与服务

    • 联系星主
    • 成长记录
    • 常见问题
    • 吐槽专区
  • 合作交流

    • 渠道合作
    • 课程入驻
    • 友情链接
author-avatar

GOLANG ROADMAP


首页
Go友会
  • 城市
  • 校园
Go学院
  • Go小课
  • Go小考
  • Go实战
  • 精品课
Go求职
  • 求职辅导🔥
  • Offer收割社群
  • 企业题库
  • 面试宝典
Go宝典
  • 在线宝典
  • B站精选
  • 推荐图书
  • 每日博文
Go仓库
实验区
  • Go周边
  • Go下载
  • Go月刊
消息
更多
  • 用户中心

    • 我的信息
    • 推广返利
  • 玩转星球

    • 星球介绍
    • 角色体系
    • 星主权益
  • 支持与服务

    • 联系星主
    • 成长记录
    • 常见问题
    • 吐槽专区
  • 合作交流

    • 渠道合作
    • 课程入驻
    • 友情链接
  • 课程介绍

    • 《Go语言基础进阶》
  • 包管理

  • IO操作

    • 第1节:File文件操作
    • 第2节:I/O操作
    • 第3节:I/O写操作
    • 第4节:文件复制
    • 第5节:断点续传
    • 第6节:bufio包
    • 第7节:ioutil包
    • 第8节:遍历文件夹
  • 并发Goroutine&Channel

  • 反射机制

扫码订阅《 Go语言基础进阶》或入驻星球,即可阅读文章!

第4节:文件复制


GOLANG ROADMAP

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

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

# 一、方法一: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

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

# 二、方法二: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)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

:::

# 扩展内容:

在io包(golang 版本 1.12)中,不止提供了()方法,还有另外2个公开的方法:N(),Buffer()。

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

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

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

1
2
3
4
5
6

他们的关系如下:

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

func Buffer(dst Writer, src Reader, buf []byte) (written int64, err error) {
    // If the reader has a WriteTo method, use it to do the .
    // Avoids an allocation and a .
    if wt, ok := src.(WriterTo); ok {
        return wt.WriteTo(dst)
    }
    // Similarly, if the writer has a ReadFrom method, use it to do the .
    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
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

从这部分代码可以看出,复制主要分为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)
    }
1
2
3
4
5
6
7
8
9
10
11

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

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

# 三、方法三: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
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 四、总结

最后,我们来测试一下这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:=File1(srcFile,destFile)
    fmt.Println(err)
    fmt.Println(total)

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

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

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

401386819

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

1
2
3
4
5
6
7
8
9

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

localhost:l_file ruby$ time go run demo05_.go 

401386819

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

1
2
3
4
5
6
7
8

第三种:ioutil包

localhost:l_file ruby$ time go run demo05_.go 

401386819

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

1
2
3
4
5
6
7
8

运行结果:

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

  • 一、方法一:io包下的Read()和Write()方法实现
  • 二、方法二:io包下的Copy()方法实现
  • 扩展内容:
  • 三、方法三:ioutil包
  • 四、总结