Excel文件解析:操作系统与应用程序的分工

文章摘要

本文介绍了操作系统和应用程序在Excel文件处理中的分工。操作系统仅负责文件存储管理和类型识别,不解析内容;而应用程序则负责解析Excel文件的具体格式。对于.xlsx文件,应用程序会先解压zip包,再解析其中的XML文件(如workbook.xml)重建表格数据。文章以C#的ExcelDataReader库为例,展示了从文件打开到数据读取的具体流程。总结指出:操作系统管文件存取,应用程序管解析内容,开发者通过调用库API实现Excel操作。


1. 操作系统的角色

  • 文件管理:操作系统负责文件的存储、打开、关闭、读写权限、文件流的分配等底层操作。
  • 文件类型识别:通过文件扩展名(.xls, .xlsx)和文件头(magic number)识别文件类型,决定用哪个应用程序打开。
  • 不负责内容解析:操作系统不关心文件内容的具体格式和结构。

2. Excel 文件的结构

  • .xls:老版 Excel,采用 Microsoft 的专有二进制格式(BIFF)。
  • .xlsx:2007 及以后,采用基于 XML 的开放文档格式(OpenXML),本质是一个 zip 压缩包,里面包含多个 XML 文件(如 workbook.xml, sheet1.xml 等)。

3. 应用程序/库如何解析 Excel 文件

3.1 以 .xlsx 为例

  1. 打开文件
    应用程序通过操作系统的文件 API(如 fopen、File.Open)获取文件流。

  2. 解压缩
    .xlsx 文件其实是一个 zip 包,应用程序先解压,获得内部的 XML 文件。

  3. 读取 XML
    解析 xl/workbook.xml(工作簿信息)、xl/worksheets/sheet1.xml(表格数据)、xl/sharedStrings.xml(字符串池)等。

  4. 解析数据结构
    通过 XML 解析器(如 System.Xml、libxml2)读取行、列、单元格内容,重建为内存中的表格对象。

  5. 数据访问
    应用程序根据需要访问、修改、保存数据。

3.2 以 C# 的 ExcelDataReader 为例

using (var stream = File.Open("test.xlsx", FileMode.Open, FileAccess.Read))
{
    using (var reader = ExcelReaderFactory.CreateOpenXmlReader(stream))
    {
        var result = reader.AsDataSet();
        // result.Tables[0] 就是第一个sheet的数据
    }
}
  • File.Open:操作系统提供的文件流。
  • ExcelReaderFactory.CreateOpenXmlReader:库内部解压并解析 XML。
  • AsDataSet:将表格数据转为 DataSet 结构,便于程序访问。

4. 总结

  • 操作系统:只负责文件的存取和流的管理,不解析内容。
  • 应用程序/库:负责识别 Excel 格式,解压、解析 XML 或二进制,重建为可用的数据结构。
  • 开发者:通过调用这些库的 API,间接实现 Excel 文件的读取和解析。

拓展阅读

  • Open XML SDK 文档
  • ExcelDataReader GitHub
  • NPOI GitHub

下面详细讲解 ExcelReaderFactory.CreateReader 的实现原理和流程。


一、ExcelReaderFactory.CreateReader 是什么?

ExcelReaderFactory.CreateReader(Stream stream) 是 ExcelDataReader 库的一个静态工厂方法。
它的作用是:根据输入的 Excel 文件流(.xls 或 .xlsx),自动识别格式,返回一个合适的 IExcelDataReader 实例,用于后续读取表格内容。


二、实现原理(源码分析)

1. 自动识别文件格式

  • ExcelDataReader 支持两种格式:
    • .xls(BIFF,二进制格式,Excel 97-2003)
    • .xlsx(OpenXml,基于 XML 的压缩包,Excel 2007+)

CreateReader 会读取文件流的前几个字节(magic number),判断是 xls 还是 xlsx。

2. 源码简化流程

(以 v3.6.0 为例,实际代码有简化)

public static IExcelDataReader CreateReader(Stream fileStream)
{
    if (fileStream == null)
        throw new ArgumentNullException(nameof(fileStream));

    // 读取前4个字节判断文件类型
    byte[] header = new byte[4];
    int bytesRead = fileStream.Read(header, 0, 4);
    fileStream.Position = 0; // 还原流位置

    // XLSX: PK\x03\x04
    if (header[0] == 0x50 && header[1] == 0x4B)
    {
        return new ExcelOpenXmlReader(fileStream); // 处理 .xlsx
    }
    // XLS: 0xD0 0xCF 0x11 0xE0
    else if (header[0] == 0xD0 && header[1] == 0xCF)
    {
        return new ExcelBinaryReader(fileStream); // 处理 .xls
    }
    else
    {
        throw new ArgumentException("Unknown Excel format");
    }
}

3. 返回的对象

  • .xlsx 返回 ExcelOpenXmlReader(实现了 IExcelDataReader)
  • .xls 返回 ExcelBinaryReader(实现了 IExcelDataReader)

这两个类都实现了统一的接口 IExcelDataReader,你可以用同样的方式读取数据。


三、使用流程

  1. 打开文件流
  2. 调用 ExcelReaderFactory.CreateReader(stream)
  3. 得到 IExcelDataReader 实例
  4. 通过 .AsDataSet().Read() 读取数据

示例:

using (var stream = File.Open("test.xlsx", FileMode.Open, FileAccess.Read))
{
    using (var reader = ExcelReaderFactory.CreateReader(stream))
    {
        var result = reader.AsDataSet();
        // 读取数据
    }
}

四、扩展说明

  • 你也可以用 CreateOpenXmlReader(只读 xlsx)或 CreateBinaryReader(只读 xls)强制指定格式。
  • CreateReader 是最常用的自动识别入口,推荐优先使用。

五、总结

  • ExcelReaderFactory.CreateReader 通过读取文件头自动识别 Excel 格式。
  • 返回统一的 IExcelDataReader 实例,便于后续无差别读取。
  • 内部实现是工厂模式+格式判断+多态。

下面将深入讲解 ExcelDataReader 库中 ExcelOpenXmlReaderExcelBinaryReader 的底层实现原理和主要流程。
(注:源码可参考 ExcelDataReader GitHub)


一、整体架构

  • IExcelDataReader:统一的接口,定义了读取 Excel 的方法(如 Read(), AsDataSet() 等)。
  • ExcelOpenXmlReader:实现了对 .xlsx(OpenXML)格式的解析。
  • ExcelBinaryReader:实现了对 .xls(BIFF)格式的解析。

ExcelReaderFactory.CreateReader 会根据文件头返回这两个类的实例。


二、ExcelOpenXmlReader(.xlsx 解析)

1. 文件结构

  • .xlsx 文件本质是一个 ZIP 压缩包,里面包含多个 XML 文件。
  • 主要文件有:
    • xl/workbook.xml:工作簿信息
    • xl/worksheets/sheet1.xml:表格数据
    • xl/sharedStrings.xml:字符串池
    • xl/styles.xml:样式信息

2. 解析流程

  1. 解压 ZIP 包

    • 使用 .NET 的 System.IO.PackagingSystem.IO.Compression 读取 ZIP 结构。
    • 定位并读取上述 XML 文件。
  2. 解析 sharedStrings.xml

    • 读取所有字符串,建立字符串池(数组)。
    • 单元格如果是字符串类型,实际存储的是 sharedStrings 的索引。
  3. 解析 workbook.xml

    • 读取所有 sheet 的名称、ID、顺序等元数据。
  4. 解析 sheetN.xml

    • 逐行读取 XML,解析每个 (cell)节点。
    • 解析单元格类型(数值、字符串、日期、布尔等)。
    • 如果是字符串,查 sharedStrings 池;如果是数值,直接读取。
  5. 实现 IExcelDataReader 接口

    • Read():移动到下一行,解析当前行的所有单元格。
    • GetValue(int i):返回当前行第 i 列的值。
    • AsDataSet():将所有 sheet 读入 DataSet。

3. 关键源码片段(伪代码)

// 1. 解压并读取 sharedStrings
var sharedStrings = ReadSharedStrings("xl/sharedStrings.xml");

// 2. 读取 workbook.xml,获取 sheet 列表
var sheets = ReadSheets("xl/workbook.xml");

// 3. 读取每个 sheetN.xml
foreach (var sheet in sheets)
{
    foreach (var row in ReadRows(sheet.XmlPath))
    {
        foreach (var cell in row.Cells)
        {
            if (cell.Type == "s") // shared string
                value = sharedStrings[cell.Value];
            else
                value = ParseCellValue(cell);
        }
    }
}

三、ExcelBinaryReader(.xls 解析)

1. 文件结构

  • .xls 是微软的 BIFF(Binary Interchange File Format)格式,二进制存储。
  • 文件由一系列“记录”(Record)组成,每个记录有类型、长度和内容。

2. 解析流程

  1. 读取文件头

    • 检查 magic number,确认是 BIFF 格式。
  2. 逐条解析 Record

    • 读取每个 Record 的类型(如 Sheet、Row、Cell、String、Format 等)。
    • 解析 Sheet 信息,建立 sheet 列表。
    • 解析 Row、Cell 记录,重建表格数据。
  3. 处理单元格数据

    • 不同类型的 Cell 记录(如 NUMBER, LABELSST, FORMULA 等)有不同的解析方式。
    • 字符串类型可能引用 SST(Shared String Table)。
  4. 实现 IExcelDataReader 接口

    • Read():移动到下一行,解析当前行的所有单元格。
    • GetValue(int i):返回当前行第 i 列的值。
    • AsDataSet():将所有 sheet 读入 DataSet。

3. 关键源码片段(伪代码)

while (stream.Position < stream.Length)
{
    var recordType = ReadUInt16();
    var recordLength = ReadUInt16();
    var recordData = ReadBytes(recordLength);

    switch (recordType)
    {
        case BOF: // 新的 sheet
            StartNewSheet();
            break;
        case ROW:
            StartNewRow();
            break;
        case NUMBER:
            ParseNumberCell(recordData);
            break;
        case LABELSST:
            ParseStringCell(recordData, sstTable);
            break;
        // ... 其他类型
    }
}

四、两者的异同

特点 ExcelOpenXmlReader (.xlsx) ExcelBinaryReader (.xls)
文件结构 ZIP+XML 二进制(BIFF)
解析方式 解压+XML流式解析 二进制流式解析
字符串存储 sharedStrings.xml SST(Shared String Table)
兼容性 Excel 2007+ Excel 97-2003
代码复杂度 XML解析 二进制协议解析

五、参考资料

  • ExcelDataReader 源码
  • OpenXML 格式说明
  • BIFF8 格式说明

六、总结

  • ExcelOpenXmlReader 通过解压 ZIP 包、解析 XML 文件流式读取 .xlsx。
  • ExcelBinaryReader 通过解析二进制 BIFF 记录流式读取 .xls。
  • 两者都实现了 IExcelDataReader,对上层调用者透明。

你可能感兴趣的:(游戏工具链,excel)