在io包中主要是操作流的一些方法,这次主要学习一下copy。就是把一个文件复制到另一个目录下。
它的原理就是通过程序,从源文件读取文件中的数据,在写出到目标文件里。
)
## 一、方法一:io包下的Read()和Write()方法实现
我们可以通过io包下的Read()和Write()方法,边读边写,就能够实现文件的复制。这个方法是按块读取文件,块的大小也会影响到程序的性能。
```go
}
/*
该函数的功能:实现文件的拷贝,返回值是拷贝的总数量(字节),错误
*/
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()方法。
示例代码如下:
```go
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()。
```go
Copy(dst,src) 为复制src 全部到 dst 中。
CopyN(dst,src,n) 为复制src 中 n 个字节到 dst。
CopyBuffer(dst,src,buf)为指定一个buf缓存区,以这个大小完全复制。
```
他们的关系如下:
)
从图可以看出,无论是哪个copy方法最终都是由copyBuffer()这个私有方法实现的。
```go
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实现复制。
其中,有这么一段代码:
```go
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()`,但由于使用一次性读取文件,再一次性写入文件的方式,所以该方法不适用于大文件,容易内存溢出。
示例代码:
```go
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),
)
代码:
```go
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()直接读写:我们自己创建读取数据的切片的大小,直接影响性能。
```go
localhost:l_file ruby$ time go run demo05_copy.go
拷贝完毕。。
<nil>
401386819
real 0m7.911s
user 0m2.900s
sys 0m7.661s
```
第二种:io包下Copy()方法:
```go
localhost:l_file ruby$ time go run demo05_copy.go
<nil>
401386819
real 0m1.594s
user 0m0.533s
sys 0m1.136s
```
第三种:ioutil包
```go
localhost:l_file ruby$ time go run demo05_copy.go
<nil>
401386819
real 0m1.515s
user 0m0.339s
sys 0m0.625s
```
运行结果:
)
这3种方式,在性能上,不管是还是io.Copy()还是ioutil包,性能都是还不错的。
它的原理就是通过程序,从源文件读取文件中的数据,在写出到目标文件里。
)
## 一、方法一:io包下的Read()和Write()方法实现
我们可以通过io包下的Read()和Write()方法,边读边写,就能够实现文件的复制。这个方法是按块读取文件,块的大小也会影响到程序的性能。
```go
}
/*
该函数的功能:实现文件的拷贝,返回值是拷贝的总数量(字节),错误
*/
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()方法。
示例代码如下:
```go
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()。
```go
Copy(dst,src) 为复制src 全部到 dst 中。
CopyN(dst,src,n) 为复制src 中 n 个字节到 dst。
CopyBuffer(dst,src,buf)为指定一个buf缓存区,以这个大小完全复制。
```
他们的关系如下:
)
从图可以看出,无论是哪个copy方法最终都是由copyBuffer()这个私有方法实现的。
```go
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实现复制。
其中,有这么一段代码:
```go
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()`,但由于使用一次性读取文件,再一次性写入文件的方式,所以该方法不适用于大文件,容易内存溢出。
示例代码:
```go
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),
)
代码:
```go
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()直接读写:我们自己创建读取数据的切片的大小,直接影响性能。
```go
localhost:l_file ruby$ time go run demo05_copy.go
拷贝完毕。。
<nil>
401386819
real 0m7.911s
user 0m2.900s
sys 0m7.661s
```
第二种:io包下Copy()方法:
```go
localhost:l_file ruby$ time go run demo05_copy.go
<nil>
401386819
real 0m1.594s
user 0m0.533s
sys 0m1.136s
```
第三种:ioutil包
```go
localhost:l_file ruby$ time go run demo05_copy.go
<nil>
401386819
real 0m1.515s
user 0m0.339s
sys 0m0.625s
```
运行结果:
)
这3种方式,在性能上,不管是还是io.Copy()还是ioutil包,性能都是还不错的。