由OutOfMemoryException异常到Excel的Xlsx文件读写

来源:互联网 发布:丁丁知乎 编辑:程序博客网 时间:2024/06/06 03:16

最近在一个项目中要用到对Excel的XLSX文件进行读写,本来直接使用第三方库NPOI来实现,在对小文件进行读写时也没遇到什么问题。但在每次读完一个小文件再读一个大文件时,就会在NPOI的XSSFWorkbook类型对象初始化中抛出System.OutOfMemoryException异常。在网上也查了相关资料,有说加内存的解决的,有说是托管代码的问题。也尝试使用EPPlus,也会出现同样的问题。采用Open-XML-SDK来解决,也有类似问题。最后,还是决定不使用第三方库,自己动手写一个XLSX文件读写库。

关于XLSX文件,通过搜索相关资料可以知道这是一种压缩格式。可以直接将XLSX文件解压缩,解压后的目录结构如下图1

继续打开xl文件夹,目录如下图2


这里就是我们主要关注的地方了。

workbook.xml文件主要关注里面的<sheets />节点,里面的子节点对应excel的每个sheet表格。通过<sheet />节点的name属性记录表格的名称。

sharedStrings.xml文件含有XLSX文件中单元格里用到的字符串。每个单元格字符串对应一个<si />节点,每个<si />节点里有至少一个<t />节点,每个<t />节点的内容就是单元格字符串的一部分。一般情况一个<si />节点下只有一个<t />节点,那么这个<t />节点的内容就是一个完整的单元格字符串;也可能出现多个<t />节点,那么就要把每个<t />节点的内容拼接起来,才形成一个单元格的字符串。

styles.xml文件记录了单元格的样式,一般不会对它进行处理。

worksheets文件夹内是Excel文件每张sheet表格对应的文件,文件名为sheet1.xml、sheet2.xml、sheet3.xml...如此类推。sheet1.xml代表第一张sheet表格的内容。重点关注<dimension />节点,她记录了网格的范围大小,如“A1”代表这张表只有一个单元格;如“A1:AA10”代表26×10大小的表格。<row />节点代表一行单元格,属性r代表从1开始的行编号。<c />节点代表某个单元格,属性r代表单元格位置,如r="A1"代表位于第1行A列,属性t表示单元格值的类型,如t="s"代表<v />节点的内容是共享字符串的索引。如果属性t没有,就表示<v />节点的内容就是单元格的值。

在读写过程中最容易出错的地方就是共享字符串的读取,如果读取不完整,就会导致解析不出正确的数据。下面是读取sharedStrings.xml文件流来获取共享字符串列表的方法

        /// <summary>        /// 读取共享字符串表单        /// </summary>        /// <param name="input">sharedStrings.xml文件流</param>        /// <returns></returns>        private static IList<string> ReadStringTable(Stream input)        {            var stringTable = new List<string>();            using (var reader = XmlReader.Create(input))            {                string meta = "";                bool added = true;                                for (reader.MoveToContent(); reader.Read(); )                {                    if (reader.NodeType == XmlNodeType.Element)                    {                        if (reader.Name == "si")                        {                            if (!added)                            {                                stringTable.Add(meta);                                added = true;                            }                            meta = "";                            added = false;                        }                        if (reader.Name == "t")                        {                            meta += reader.ReadElementString();                        }                    }                    else if (reader.NodeType == XmlNodeType.EndElement)                    {                        if (reader.Name == "si")                        {                            if (!added)                            {                                stringTable.Add(meta);                                added = true;                            }                        }                        else if (reader.Name == "sst")                        {                            if (!added)                            {                                stringTable.Add(meta);                                added = true;                            }                        }                    }                                  }            }                      return stringTable;        }

完整的项目文件可以从这里获取https://github.com/Nick-PAN/PdkXlsxFileIO