导出:对象怎样变成 ZIP 输出 高层入口可以很简单// Foo 与 foos 的定义见第 00 篇。Xlsx.Write(“foo.xlsx”, foos);await Xlsx.WriteAsync(response.Body, asyncFoos, cancellationToken: ct);内部写入路径大致如下IEnumerable / IAsyncEnumerable↓ExportProfile↓ 冻结配置构建 RowPlan列元数据 / getter / cell writer / 样式↓XlsxWriter↓ 逐行生成 worksheet XMLByteBufferWriter↓ 局部 XML 字节缓冲ForwardOnlyZipWriter↓ Local Header / 数据 / Data DescriptorStream / IBufferWriter / 文件这里有两类状态逐行状态当前 row、当前 cell、局部 XML 缓冲工作簿状态styles、sheet 信息、SST、Central Directory 元数据逐行状态可以持续推进工作簿状态要到完成时才能写出全部内容。比如启用 SST 时唯一字符串表必须保存到结束ZIP 的 Central Directory 也要等所有 entry 完成后写入。导入ZIP 怎样变回对象读取入口对应另一条方向// Foo 的定义见第 00 篇。foreach (var foo in Xlsx.Read(stream)){// 按行消费}await foreach (var foo in Xlsx.ReadAsync(stream, cancellationToken: ct)){// 异步按行消费}Reader 的主路径是Stream↓ZipArchive├─ workbook.xml / workbook.xml.rels├─ date1904└─ sharedStrings.xml如存在↓第一个可读 worksheet XML↓ XmlReader 逐行读取稀疏列 / inline string / SST / 富文本↓XlsxReadPipeline表头 / 显式映射 / Converter / Source Generator↓T导入同样不是“整个文件都读入内存”。工作表 XML 行可以逐行消费但 Shared String Table 会整体加载输入流、ZIP 解压、当前行缓冲和业务对象也都占用内存。读写共享的结构约束导入和导出并不是两套无关代码它们都受同一份 OOXML 结构约束结构 导出侧 导入侧工作簿关系workbook relationship 写 workbook 与 sheet 的关系 通过关系定位第一个 worksheet单元格引用 写 A1、B1 等位置 由引用恢复稀疏列位置inline string / SST 选择字符串存储策略 还原字符串文本日期系统 写 Excel 日期值与格式 读取 date1904 后解析日期ZIP entry 顺序写 Data Descriptor 打开 entry 并读 XML对象映射 属性写成列 表头和列写回属性这也是本文后续按“包结构、单元格、字符串、映射、异步”来组织而不是把导入和导出拆成两套薄文章的原因。低分配的边界写入侧的热点路径会尽量减少临时数组和字符串池化字节缓冲、UTF-8 直接格式化、固定 XML 片段和生成的 cell writer 都服务于这个目标。但“低分配”不是整条 I/O 管线的通行证ToBytes 必然物化最终 byte[]netstandard2.0 有兼容分支SST 会维护全量唯一字符串表Reader 会加载 sharedStrings真正异步 I/O 有状态机和底层流成本DTO 映射仍会创建业务对象。因此文章中的性能结论必须限定到具体 API、TFM、数据形态和输出/输入目标。流所有权也不同API 默认所有权Xlsx.Write(Stream, …) 调用方负责释放输出 streamXlsx.WriteAsync(Stream, …) 调用方负责释放输出 streamXlsx.Read(Stream, …) 枚举结束时 Reader 默认关闭输入 streamXlsx.ReadAsync(Stream, …) 枚举结束时 Reader 默认关闭输入 stream读取时如果调用方仍要使用输入流应传 leaveOpen: true。输出响应开始写入后也不能回滚已发送的 ZIP 字节这些生命周期问题和性能