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:\Users\Administrator\Desktop\大數據.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 }
感謝:
https://www.cnblogs.com/longshanshan/p/7156036.html