ioutil包在go1.16版本已弃用。
io.ReadAll()实现:
// src/io/io.go
func ReadAll(r Reader) ([]byte, error) {
// 创建一个 512 字节的 buf
b := make([]byte, 0, 512)
for {
if len(b) == cap(b) {
// 如果 buf 满了,则追加一个元素,使其重新分配内存
b = append(b, 0)[:len(b)]
}
// 读取内容到 buf
n, err := r.Read(b[len(b):cap(b)])
b = b[:len(b)+n]
// 遇到结尾或者报错则返回
if err != nil {
if err == EOF {
err = nil
}
return b, err
}
}
}
我给代码加上了必要的注释,这段代码的执行主要分三个步骤:
- 创建一个 512 字节的
buf
; - 不断读取内容到
buf
,当buf
满的时候,会追加一个元素,促使其重新分配内存; - 直到结尾或报错,则返回;
知道了执行步骤,但想要分析其性能问题,还需要了解 Go 切片的扩容策略,如下:
- 如果期望容量大于当前容量的两倍就会使用期望容量;
- 如果当前切片的长度小于 1024 就会将容量翻倍;
- 如果当前切片的长度大于 1024 就会每次增加 25% 的容量,直到新容量大于期望容量;
也就是说,如果待拷贝数据的容量小于 512 字节的话,性能不受影响。但如果超过 512 字节,就会开始切片扩容。数据量越大,扩容越频繁,性能受影响越大。
如果数据量足够大的话,内存可能就直接撑爆了,这样的话影响就大了。
那有更好的替换方案吗?当然是有的,我们接着往下看。
io.Copy
可以使用 io.Copy
函数来代替
对比之后就会发现,io.Copy
函数不会一次性读取全部数据,也不会频繁进行切片扩容,显然在数据量大时是更好的选择。
实现方式:
package main
import (
"fmt"
"io"
"net/http"
"os"
)
func main() {
fmt.Println("嗨客网()")
for _, url := range os.Args[1:] {
resp, err := http.Get(url)
if err != nil {
fmt.Fprintf(os.Stderr, "fetch: %v\n", err)
os.Exit(1)
}
b, err := io.Copy(os.Stdout, resp.Body)
resp.Body.Close()
if err != nil {
fmt.Fprintf(os.Stderr, "fetch: %v\n", err)
os.Exit(1)
}
fmt.Printf("%s", b)
}
}
函数 调用 io.Copy(dst, src) 会从 src 中读取内容,并将读到的结果写入到 dst 中,使用这个函数替代掉例子中的 ioutil.ReadAll 来拷贝响应 结构体 到 os.Stdout,避免申请一个缓冲区(例子中的 b)来存储。记得处理 io.Copy 返回结果中的错误。
标签:ReadAll,512,err,os,io,Go,Copy,buf From: https://blog.51cto.com/lookingdream/9294311