Skip to content

io.ReadAll impact the OpenReader function memory usage #2086

Closed
@ijustyce

Description

@ijustyce

OpenReader 源码如下:

func OpenReader(r io.Reader, opts ...Options) (*File, error) {
	b, err := io.ReadAll(r)
	if err != nil {
		return nil, err
	}
	f := newFile()
	f.options = f.getOptions(opts...)
	if err = f.checkOpenReaderOptions(); err != nil {
		return nil, err
	}
	if bytes.Contains(b, oleIdentifier) {
		if b, err = Decrypt(b, f.options); err != nil {
			return nil, ErrWorkbookFileFormat
		}
	}
	zr, err := zip.NewReader(bytes.NewReader(b), int64(len(b)))
	if err != nil {
		if len(f.options.Password) > 0 {
			return nil, ErrWorkbookPassword
		}
		return nil, err
	}
	file, sheetCount, err := f.ReadZipReader(zr)
	if err != nil {
		return nil, err
	}
	f.SheetCount = sheetCount
	for k, v := range file {
		f.Pkg.Store(k, v)
	}
	if f.CalcChain, err = f.calcChainReader(); err != nil {
		return f, err
	}
	if f.sheetMap, err = f.getSheetMap(); err != nil {
		return f, err
	}
	if f.Styles, err = f.stylesReader(); err != nil {
		return f, err
	}
	f.Theme, err = f.themeReader()
	return f, err
}

其中 io.ReadAll 会导致将所有数据加载到内存,这和流式读取的初衷违背;这里 io.ReadAll 的结果 b 的使用相关代码是:

if bytes.Contains(b, oleIdentifier) {
	if b, err = Decrypt(b, f.options); err != nil {
		return nil, ErrWorkbookFileFormat
	}
}
zr, err := zip.NewReader(bytes.NewReader(b), int64(len(b)))

建议将这串代码直接改为:

zr, err := zip.OpenReader(name)

并将 file, sheetCount, err := f.ReadZipReader(zr) 改为 file, sheetCount, err := f.ReadZipReader(&zr.Reader) 即可实现真正的流式读取。
我在本地 replace 后测试,修改后,读取 7.8G 的 excel 文件,内存占用不到 10M,而修改前 32G 内存占用。

但并不确定如下代码的用途:

if bytes.Contains(b, oleIdentifier) {
	if b, err = Decrypt(b, f.options); err != nil {
		return nil, ErrWorkbookFileFormat
	}
}

该代码看起来是兼容老版本的 Excel,但具体我并不清楚,在我的测试中,Excel 文件后缀为:xlsx,且读取的数据和实际数据一致。

所以,我的建议是,能否按上面的修改来确保流式读取是真正的流式读取,而不是先 io.ReadAll 全部读取到内存,然后才流式读取,这无意义。如果上面我不确定用途的代码,有实际含义,那么能否在流式读取中兼容它?如果不兼容,那么能否提供不兼容部分格式的流式读取呢?

类似的 issue 有:https://github.com/qax-os/excelize/issues/1775,但该 issue 已经关闭。

Metadata

Metadata

Assignees

No one assigned

    Labels

    duplicateThis issue or pull request already exists

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions