ASP.NET下C#读取Excel文件,有好几种方法,我了解到的有:Microsoft.Office.Interop.Excel.dll 、 Microsoft.Jet.OLEDB 、NPOI,其中NPOI应该是用的比较多的吧,我个人来说比较倾向使用NPOI,很方便。不过今天我的一个小伙伴突然微信我,说她现在的公司需要解析一个上百兆的Excel文件,使用NPOI会有内存溢出的问题,即使根据需求将文件大小控制在最小50M以内还是不行,问我有什么办法能解决这个问题。
这个问题虽然我没做深入的了解,但是按照经验来看很可能是NPOI的瓶颈,或者说是她用的这个版本NPOI版本的瓶颈。
那么这个问题怎么解决呢?上菜!
DocumentFormat.OpenXmlSDK
对,没错!就是他,微软提供的一个读取Excel的类库
1、通过NuGet搜索 DocumentFormat.OpenXml
我下载的是第二个,至于为啥是第二个,因为小.... 而且对.NetFramework版本没有依赖
2、解析Excel
1 /// <summary> 2 /// 获取Excel指定工作表数据 3 /// </summary> 4 /// <param name="filePath">Excel所在路径</param> 5 /// <param name="sheetName">工作表名</param> 6 /// <returns></returns> 7 public static void GetExcelVlaue(string filePath, string sheetName) 8 { 9 //打开文件 10 SpreadsheetDocument document = SpreadsheetDocument.Open(filePath, false); 11 WorkbookPart workbook = document.WorkbookPart; 12 IEnumerable<Sheet> sheets = document.WorkbookPart.Workbook.Descendants<Sheet>().Where(s => s.Name == sheetName);//此处改成读取第一个sheet页面即可 13 if (sheets.Count() == 0) 14 { 15 //sheet空判断 16 } 17 WorksheetPart worksheetPart = (WorksheetPart)document.WorkbookPart.GetPartById(sheets.First().Id); 18 Worksheet worksheet = worksheetPart.Worksheet; 19 IEnumerable<Row> rows = worksheet.Descendants<Row>(); 20 foreach (Row row in rows)//获取行的值 21 { 22 foreach (Cell cell in row) 23 { 24 string columnValue = GetValue(cell, workbook.SharedStringTablePart); 25 } 26 } 27 }
1 /// <summary> 2 /// 获取单元格信息 这也是官方获取值的方法 3 /// </summary> 4 /// <param name="cell"></param> 5 /// <param name="stringTablePart">stringTablePart就是WorkbookPart.SharedStringTablePart,它存储了所有以SharedStringTable方式存储数据的子元素。</param> 6 /// <returns></returns> 7 public static string GetValue(Cell cell, SharedStringTablePart stringTablePart) 8 { 9 if (cell.ChildElements.Count == 0) 10 return null; 11 //get cell value 12 String value = cell.CellValue.InnerText; 13 //Look up real value from shared string table 14 if ((cell.DataType != null) && (cell.DataType == CellValues.SharedString)) 15 value = stringTablePart.SharedStringTable 16 .ChildElements[Int32.Parse(value)] 17 .InnerText; 18 return value; 19 }
亲测可用,没毛病!
400M左右的文件测试三次,数据量在50W条左右,耗时平均在00:01:10左右。
100M左右的文件测试一次,数据量在100W条左右,耗时00:03:47左右。
1 public void Read() 2 { 3 DataTable dt = new DataTable(); 4 Stopwatch watch = new Stopwatch(); 5 watch.Start(); 6 using (SpreadsheetDocument spreadSheetDocument = SpreadsheetDocument.Open(@"C:UsersAdministratorDesktop大数据.xlsx", false)) 7 { 8 WorkbookPart workbookPart = spreadSheetDocument.WorkbookPart; 9 IEnumerable<Sheet> sheets = spreadSheetDocument.WorkbookPart.Workbook.GetFirstChild<Sheets>().Elements<Sheet>(); 10 string relationshipId = sheets.First().Id.Value = sheets.First(x => x.Name == "Sheet1").Id.Value; 11 WorksheetPart worksheetPart = (WorksheetPart)spreadSheetDocument.WorkbookPart.GetPartById(relationshipId); 12 Worksheet workSheet = worksheetPart.Worksheet; 13 SheetData sheetData = workSheet.GetFirstChild<SheetData>(); 14 Row[] rows = sheetData.Descendants<Row>().ToArray(); 15 16 int count = rows.Count(); 17 string time1 = watch.Elapsed.ToString(); 18 log.Error("excel解析完成,数据条数:" + count + ",耗时:" + time1); 19 20 watch.Restart(); 21 // 设置表头DataTable 22 foreach (Cell cell in rows.ElementAt(0)) 23 { 24 dt.Columns.Add((string)GetCellValue(spreadSheetDocument, cell)); 25 } 26 27 // 添加内容 28 for (int rowIndex = 1; rowIndex < rows.Count(); rowIndex++) 29 { 30 DataRow tempRow = dt.NewRow(); 31 32 for (int i = 0; i < rows[rowIndex].Descendants<Cell>().Count(); i++) 33 { 34 tempRow[i] = GetCellValue(spreadSheetDocument, rows[rowIndex].Descendants<Cell>().ElementAt(i)); 35 } 36 dt.Rows.Add(tempRow); 37 } 38 39 string time2 = watch.Elapsed.ToString(); 40 log.Error("data生成结束,耗时:" + time2); 41 } 42 }
1 public static string GetCellValue(SpreadsheetDocument document, Cell cell) 2 { 3 SharedStringTablePart stringTablePart = document.WorkbookPart.SharedStringTablePart; 4 string value = cell.CellValue.InnerXml; 5 6 if (cell.DataType != null && (cell.DataType.Value == CellValues.SharedString || cell.DataType.Value == CellValues.String || cell.DataType.Value == CellValues.Number)) 7 { 8 return stringTablePart.SharedStringTable.ChildElements[Int32.Parse(value)].InnerText; 9 } 10 else //浮点数和日期对应的cell.DataType都为NULL 11 { 12 // DateTime.FromOADate((double.Parse(value)); 如果确定是日期就可以直接用过该方法转换为日期对象,可是无法确定DataType==NULL的时候这个CELL 数据到底是浮点型还是日期.(日期被自动转换为浮点 13 return value; 14 } 15 }
感谢: