首页 > 解决方案 > strings.Builder 内存使用情况

问题描述

使用此文件(数据文件):

package main

import (
   "io/ioutil"
   "time"
)

func main() {
   ioutil.ReadFile("100mb.file")
   time.Sleep(time.Duration(time.Minute))
}

显示我的内存使用量为 107 MB。使用这个类似的文件:

package main

import (
   "bytes"
   "os"
   "time"
)

func read(path_s string) (bytes.Buffer, error) {
   buf_o := bytes.Buffer{}
   open_o, e := os.Open(path_s)
   if e != nil {
      return buf_o, e
   }
   buf_o.ReadFrom(open_o)
   open_o.Close()
   return buf_o, nil
}

func main() {
   read("100mb.file")
   time.Sleep(time.Duration(time.Minute))
}

内存使用量达到 273 MB。最后这个类似的文件:

package main

import (
   "io"
   "os"
   "strings"
   "time"
)

func read(path_s string) (strings.Builder, error) {
   str_o := strings.Builder{}
   open_o, e := os.Open(path_s)
   if e != nil {
      return str_o, e
   }
   io.Copy(&str_o, open_o)
   open_o.Close()
   return str_o, nil
}

func main() {
   read("100mb.file")
   time.Sleep(time.Duration(time.Minute))
}

内存使用量达到 432 MB。我尽量小心并尽可能关闭文件。为什么第二个示例的内存使用率如此之高,尤其是最后一个示例?我可以改变一些东西,使它们更接近第一个例子吗?

标签: stringgomemorybytebuffer

解决方案


ioutil.ReadFile("100mb.file")获取文件的大小,分配一个[]byte该大小并将字节吞入该切片。

buf_o.ReadFrom(open_o)[]byte分配一些大小的初始值并读入该切片。如果读取器中的数据多于切片中的空间,则该函数分配一个更大的切片,将现有数据复制到该切片并读取更多数据。这将重复直到 EOF。

函数 ioutil.ReadFile 在内部使用字节 Buffer.ReadFrom。查看 ioutil.ReadFile 实现,了解如何改进对 bytes.Buffer 的直接使用。逻辑概要是这样的:

var buf bytes.Buffer

// Open file
f, err := os.Open(path)
if err != nil {
    return &buf, err
}
defer f.Close()

// Get size.
fi, err := f.Stat()
if err != nil {
    return &buf, err
}

// Grow to size of file plus extra slop to ensure no realloc.
buf.Grow(int(fi.Size()) + bytes.MinRead)

_, err := buf.ReadFrom(f)
return &buf, err

strings.Builder 示例像 bytes.Buffer 示例一样多次重新分配内部缓冲区。此外,io.Copy 分配一个缓冲区。您可以通过在读取之前将构建器增大到文件大小来改进 strings.Builder 示例。

这是strings.Builder的代码:

var buf strings.Builder

// Open file
f, err := os.Open(path)
if err != nil {
    return &buf, err
}
defer f.Close()

// Get size.
fi, err := f.Stat()
if err != nil {
    return &buf, err
}

buf.Grow(int(fi.Size()))

_, err = io.Copy(&buf, f)
return &buf, err

io.Copy 或其他使用额外缓冲区的代码是必需的,因为 strings.Builder 没有 ReadFrom 方法。strings.Builder 类型没有 ReadFrom 方法,因为该方法可能泄漏对内部字节切片的支持数组的引用。


推荐阅读