ioutil.ReadAll 是一个常用的数据读取方法,经常用来读取http请求的response数据,或者读取文件数据。
demo1: http
1 2 3 4 5 6 7 | func handle(r *http.Request, w http.ResponseWriter) { file, err := os.Open("tmp.zip") // error checks... b, err := ioutil.ReadAll(file) // error checks fmt.FPrintf(w, b) } |
demo2 : 模拟shell md5sum 获取文件MD5
1 2 3 4 5 6 7 8 9 | func genmd5(file string) string{ cf, err := os.Open(file) //err check defer cf.Close() body, err := ioutil.ReadAll(cf) //err check MD5Str := fmt.Sprintf("%x", md5.Sum(body)) return MD5Str } |
上述两个例子看起来都没什么问题,但是当文件数据特别大的时候,ioutil.ReadAll会将全部的数据加载到内存,对于demo1,在高并发的情况下,最终会导致服务因为内存不足而崩溃。demo2会将整个文件加载到内存,并且短时无法清理(2G文件生成MD5后,内存大约7min后才会gc完成)。
在这种情况下最好使用io.Copy的方式代替 ioutil.ReadAll
demo1 优化:
1 2 3 4 5 | func handle(r *http.Request, w http.ResponseWriter) { file, err := os.Open("tmp.zip") // error checks... io.Copy(w, file) } |
Demo2 优化
1 2 3 4 5 6 7 8 9 10 11 12 | func genmd5(file string) string{ f, err := os.Open(file) // err check defer f.Close() // 改用io.Writer对象获取文件数据 md5hash := md5.New() if _, err := io.Copy(md5hash, f); err != nil { // err check } MD5Str := fmt.Sprintf("%x", md5hash.Sum(nil)) return MD5Str } |
io.Copy使用固定的32K缓冲区,因此无论源数据多大,都只会占用32K内存空间。
所以需要向io.Writer写入时,建议优先使用io.Copy的方式。