关于go:一文了解-ioCopy-函数

37次阅读

共计 4514 个字符,预计需要花费 12 分钟才能阅读完成。

1. 引言

io.Copy 函数是一个十分好用的函数,可能十分不便得将数据进行拷贝。本文咱们将从io.Copy 函数的根本定义登程,讲述其根本应用和实现原理,以及一些注意事项,基于此实现对io.Copy 函数的介绍。

2. 根本阐明

2.1 根本定义

Copy函数用于将数据从源(io.Reader)复制到指标(io.Writer)。它会继续复制直到源中的数据全副读取结束或产生谬误,并返回复制的字节数和可能的谬误。函数定义如下:

func Copy(dst io.Writer, src io.Reader) (written int64, err error)

其中 dst 为指标写入器,用于接管源数据;src 则是源读取器,用于提供数据。

2.2 应用示例

上面提供一个应用 io.Copy 实现数据拷贝的代码示例,比便更好得了解和应用 Copy 函数,代码示例如下:

package main

import (
        "fmt"
        "io"
        "os"
)

func main() {fmt.Print("请输出一个字符串:")
        src := readString()
        // 通过 io.Copy 函数可能将 src 的全副数据 拷贝到 管制台上输入
        written, err := io.Copy(os.Stdout, src)
        if err != nil {fmt.Println("复制过程中产生谬误:", err)
                return
        }

        fmt.Printf("\n 胜利复制了 %d 个字节。\n", written)
}

func readString() io.Reader {buffer := make([]byte, 1024)
   n, _ := os.Stdin.Read(buffer)
   // 如果理论读取的字节数少于切片长度,则截取切片
   if n < len(buffer) {buffer = buffer[:n]
   }
   return strings.NewReader(string(buffer))
}

在这个例子中,咱们首先应用 readString 函数从规范输出中读取字符串,而后应用 strings.NewReader 将其包装为 io.Reader 返回。

而后,咱们调用 io.Copy 函数,将读取到数据全副复制到规范输入(os.Stdout)。最初,咱们打印复制的字节数。能够运行这个程序并在终端输出一个字符串,通过 Copy 函数,程序最终会将字符串打印到终端上。

3. 实现原理

在理解了io.Copy 函数的根本定义和应用后,这里咱们来对 io.Copy 函数的实现来进行根本的阐明,加深对 io.Copy 函数的了解。

io.Copy根本实现原理如下,首先创立一个缓冲区,用于暂存从源 Reader 读取到的数据。而后进入一个循环,每次循环从源 Reader 读取数据,而后存储到之前创立的缓冲区,之后再写入到指标 Writer 中。一直反复这个过程,直到源 Reader 返回 EOF,此时代表数据曾经全副读取实现,io.Copy也实现了从源 Reader 往指标 Writer 拷贝全副数据的工作。

在这个过程中,如果往指标 Writer 写入数据过程中产生谬误,亦或者从源 Reader 读取数据产生谬误,此时 io.Copy 函数将会中断,而后返回对应的谬误。上面咱们来看 io.Copy 的实现:

func Copy(dst Writer, src Reader) (written int64, err error) {
   // Copy 函数 调用了 copyBuffer 函数来实现
   return copyBuffer(dst, src, nil)
}

func copyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) {
   // 如果 源 Reader 实现了 WriterTo 接口, 间接调用该办法 将数据写入到 指标 Writer 当中
   if wt, ok := src.(WriterTo); ok {return wt.WriteTo(dst)
   }
   // 同理,如果 指标 Writer 实现了 ReaderFrom 接口, 间接调用 ReadFrom 办法
   if rt, ok := dst.(ReaderFrom); ok {return rt.ReadFrom(src)
   }
   // 如果没有传入缓冲区,此时默认 创立一个 缓冲区
   if buf == nil {
      // 默认缓冲区 大小为 32kb
      size := 32 * 1024
      // 如果源 Reader 为 LimitedReader, 此时比拟 可读数据数 和 默认缓冲区,取较小那个
      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 {
      // 调用 Read 办法 读取数据
      nr, er := src.Read(buf)
      if nr > 0 {
         // 将数据写入到 指标 Writer 当中
         nw, ew := dst.Write(buf[0:nr])
         // 判断写入是否 呈现了 谬误
         if nw < 0 || nr < nw {
            nw = 0
            if ew == nil {ew = errInvalidWrite}
         }
         // 累加 总写入数据
         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
}

从上述基本原理和代码实现来看,io.Copy 函数的实现还是非常简单的,就是申请一个缓冲区,而后从源 Reader 读取一些数据放到缓冲区中,而后再将缓冲区的数据写入到 指标 Writer,如此往返,直到数据全副读取实现。

4. 注意事项

4.1 留神敞开源 Reader 和指标 Writer

在应用io.Copy 进行数据拷贝时,须要指定源 Reader 和 指标 Writer,当io.Copy 实现数据拷贝工作后,咱们须要调用Close 办法敞开 源 Reader 和 指标 Writer。如果没有适时敞开资源,可能会导致一些不可意料状况的呈现。

上面展现一个应用 io.Copy 进行文件复制的代码示例,同时简略阐明不适时敞开资源可能导致的问题:

package main

import (
        "fmt"
        "io"
        "os"
)

func main() {
        sourceFile := "source.txt"
        destinationFile := "destination.txt"

        // 关上源文件
        src, err := os.Open(sourceFile)
        if err != nil {fmt.Println("无奈关上源文件:", err)
                return
        }
        // 调用 Close 办法
        defer src.Close()

        // 创立指标文件
        dst, err := os.Create(destinationFile)
        if err != nil {fmt.Println("无奈创立指标文件:", err)
                return
        }
        // 调用 Close 办法
        defer dst.Close()

        // 执行文件复制
        _, err = io.Copy(dst, src)
        if err != nil {fmt.Println("复制文件出错:", err)
                return
        }

        fmt.Println("文件复制胜利!")
}

应用 io.Copy 函数将源文件的内容复制到指标文件中。在完结代码之前,咱们须要适时地敞开源文件和指标文件。以下面应用io.Copy 实现文件复制性能为例,如果咱们没有适时敞开资源,首先是可能会导致文件句柄透露,数据不残缺等一系列问题的呈现。

因而咱们在 io.Copy 函数之后,须要在适当的中央调用 Close 关闭系统资源。

4.2 思考性能问题

io.Copy 函数默认应用一个 32KB 大小的缓冲区来复制数据,如果咱们解决的是大型文件,亦或者是高性能要求的场景,此时是能够思考间接应用 io.CopyBuffer 函数,自定义缓冲区大小,以优化复制性能。而io.Copyio.CopyBuffer 底层其实都是调用io.copyBuffer 函数的,二者底层实现其实没有太大的区别。

上面通过一个基准测试,展现不同缓冲区大小对数据拷贝性能的影响:

func BenchmarkCopyWithBufferSize(b *testing.B) {
   // 本地运行时, 文件大小为 100 M
   filePath := "largefile.txt"
   bufferSizes := []int{32 * 1024, 64 * 1024, 128 * 1024} // 不同的缓冲区大小

   for _, bufferSize := range bufferSizes {b.Run(fmt.Sprintf("BufferSize-%d", bufferSize), func(b *testing.B) {
         for n := 0; n < b.N; n++ {src, _ := os.Open(filePath)
            dst, _ := os.Create("destination.txt")

            buffer := make([]byte, bufferSize)
            _, _ = io.CopyBuffer(dst, src, buffer)

            _ = src.Close()
            _ = dst.Close()
            _ = os.Remove("destination.txt")
         }
      })
   }
}

这里咱们定义的缓冲区大小别离是 32KB, 64KB 和 128KB,而后应用该缓冲区来拷贝数据。上面咱们看基准测试的后果:

BenchmarkCopyWithBufferSize/BufferSize-32768-4                        12         116494592 ns/op
BenchmarkCopyWithBufferSize/BufferSize-65536-4                        10         110496584 ns/op
BenchmarkCopyWithBufferSize/BufferSize-131072-4                       12          87667712 ns/op

从这里看来,32KB 大小的缓冲区拷贝一个 100M 的文件,须要116494592 ns/op, 而 128KB 大小的缓冲区拷贝一个 100M 的文件,须要87667712 ns/op。不同缓冲区的大小,的确是会对拷贝的性能有肯定的影响。

在理论应用中,依据文件大小、系统资源和性能需求,能够依据需要进行缓冲区大小的调整。较小的文件通常能够间接应用io.Copy 函数默认的 32KB 缓冲区,而较大的文件可能须要更大的缓冲区来进步性能。通过正当抉择缓冲区大小,能够取得更高效的文件复制操作。

5. 总结

io.Copy 函数是 Go 语言规范库提供的一个工具函数,可能将数据从源 Reader 复制到指标 Writer。咱们先从io.Copy 函数的根本定义登程,之后通过一个简略的示例,展现如何应用io.Copy 函数实现数据拷贝。

接着咱们讲述了io.Copy 函数的实现原理,其实就是定义了一个缓冲区,将源 Reader 数据写入到缓冲区中,而后再将缓冲区的数据写入到指标 Writer,一直反复这个过程,实现了数据的拷贝。

在注意事项方面,则强调了及时敞开源 Reader 和指标 Writer 的重要性。以及用户在应用时,须要思考 io.Copy 函数的性能是否可能满足要求,之后通过基准测试展现了不同缓冲区大小可能带来的性能差距。

基于此,实现了对io.Copy 函数的介绍,心愿对你有所帮忙。

正文完
 0