C#读取Excel的三种方式以及比较分析
(1)OleDB方式
优点:将Excel直接当做数据源处理,通过SQL直接读取内容,读取速度较快。
缺点:读取数据方式不够灵活,无法直接读取某一个单元格,只有将整个Sheet页读取出来后(结果为Datatable)再在Datatable中根据行列数来获取指定的值。
当Excel数据量很大时。会非常占用内存,当内存不够时会抛出内存溢出的异常。
读取代码如下:
publicDataTableGetExcelTableByOleDB(stringstrExcelPath,stringtableName) { try { DataTabledtExcel=newDataTable(); //数据表 DataSetds=newDataSet(); //获取文件扩展名 stringstrExtension=System.IO.Path.GetExtension(strExcelPath); stringstrFileName=System.IO.Path.GetFileName(strExcelPath); //Excel的连接 OleDbConnectionobjConn=null; switch(strExtension) { case".xls": objConn=newOleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;DataSource="+strExcelPath+";"+"ExtendedProperties=\"Excel8.0;HDR=NO;IMEX=1;\""); break; case".xlsx": objConn=newOleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;DataSource="+strExcelPath+";"+"ExtendedProperties=\"Excel12.0;HDR=NO;IMEX=1;\""); break; default: objConn=null; break; } if(objConn==null) { returnnull; } objConn.Open(); //获取Excel中所有Sheet表的信息 //System.Data.DataTableschemaTable=objConn.GetOleDbSchemaTable(System.Data.OleDb.OleDbSchemaGuid.Tables,null); //获取Excel的第一个Sheet表名 //stringtableName=schemaTable.Rows[0][2].ToString().Trim(); stringstrSql="select*from["+tableName+"]"; //获取Excel指定Sheet表中的信息 OleDbCommandobjCmd=newOleDbCommand(strSql,objConn); OleDbDataAdaptermyData=newOleDbDataAdapter(strSql,objConn); myData.Fill(ds,tableName);//填充数据 objConn.Close(); //dtExcel即为excel文件中指定表中存储的信息 dtExcel=ds.Tables[tableName]; returndtExcel; } catch { returnnull; } }
下面说明一下连接字符串
HDR=Yes,这代表第一行是标题,不做为数据使用(但是我在实际使用中,如果第一行存在复杂数值,那么读取得到的Datatable列标题会自动设置为F1、F2等方式命名,与实际应用不符,所以当时是通过HDR=No方式将所有内容读取到Datatable中,然后手动将第一行设置成标题的);IMEX(IMportEXportmode)设置
IMEX有三种模式:
0isExportmode
1isImportmode
2isLinkedmode(fullupdatecapabilities)
我这里特别要说明的就是IMEX参数了,因为不同的模式代表著不同的读写行为:
当IMEX=0时为“汇出模式”,这个模式开启的Excel档案只能用来做“写入”用途。
当IMEX=1时为“汇入模式”,这个模式开启的Excel档案只能用来做“读取”用途。
当IMEX=2时为“链接模式”,这个模式开启的Excel档案可同时支援“读取”与“写入”用途。
---------------------------------
另外,读取Excel2007版本的文件时,版本应该从8.0改为12.0,同时驱动不能再用Jet,而应该用ACE。负责会造成“找不到可安装的ISAM”的错误。
---------------------------------
在网上还发现采用这种方式存在取出的Sheet表的个数多于实际Excel表中的Sheet表个数的情况,其原因有二:
1.取出的名称中,包括了XL命名管理器中的名称(参见XL2007的公式--命名管理器,快捷键Crtl+F3);
2.取出的名称中,包括了FilterDatabase后缀的,这是XL用来记录Filter范围的。
对于第一点比较简单,删除已有命名管理器中的内容即可;第二点处理起来比较麻烦,Filter删除后这些名称依然保留着,简单的做法是新增Sheet然后将原SheetCopy进去。但实际情况并不能为每个Excel做以上检查。下面给出了过滤的方案。(此问题我们有验证过,大家自己验证一下吧)
//objConn为读取Excel的链接,下面通过过滤来获取有效的Sheet页名称集合 System.Data.DataTableschemaTable=objConn.GetOleDbSchemaTable(System.Data.OleDb.OleDbSchemaGuid.Tables,null); List<string>lstSheetNames=newList<string>(); for(inti=0;i<schemaTable.Rows.Count;i++) { stringstrSheetName=(string)dtSheetName.Rows[i]["TABLE_NAME"]; if(strSheetName.Contains("$")&&!strSheetName.Replace("'","").EndsWith("$")) { //过滤无效SheetName完毕.... continue; } if(lstSheetNames!=null&&!lstSheetNames.Contains(strSheetName)) lstSheetNames.Add(strSheetName); }
因为读取出来无效SheetName一般情况最后一个字符都不会是$。如果SheetName有一些特殊符号,读取出来的SheetName会自动加上单引号。比如在Excel中将SheetName编辑成MySheet(1),此时读取出来的SheetName就为:'MySheet(1)$',所以判断最后一个字符是不是$之前最好过滤一下单引号。
---------------------------------
(2)Com组件的方式(通过添加Microsoft.Office.Interop.Excel引用实现)
优点:能够非常灵活的读取Excel中的数据,用户可以灵活的调用各种函数进行处理。
缺点:基于单元格的处理,读取速度较慢,对于数据量较大的文件最好不要使用此种方式读取。
需要添加相应的DLL引用,必须存在此引用才可使用,如果是Web站点部署在IIS上时,还需要服务器机子已安装了Excel,有时候还需要为配置IIS权限。
读取代码如下:
privateStopwatchwath=newStopwatch(); ///<summary> ///使用COM读取Excel ///</summary> ///<paramname="excelFilePath">路径</param> ///<returns>DataTabel</returns> publicSystem.Data.DataTableGetExcelData(stringexcelFilePath) { Excel.Applicationapp=newExcel.Application(); Excel.Sheetssheets; Excel.Workbookworkbook=null; objectoMissiong=System.Reflection.Missing.Value; System.Data.DataTabledt=newSystem.Data.DataTable(); wath.Start(); try { if(app==null) { returnnull; } workbook=app.Workbooks.Open(excelFilePath,oMissiong,oMissiong,oMissiong,oMissiong,oMissiong, oMissiong,oMissiong,oMissiong,oMissiong,oMissiong,oMissiong,oMissiong,oMissiong,oMissiong); //将数据读入到DataTable中——Start sheets=workbook.Worksheets; Excel.Worksheetworksheet=(Excel.Worksheet)sheets.get_Item(1);//读取第一张表 if(worksheet==null) returnnull; stringcellContent; intiRowCount=worksheet.UsedRange.Rows.Count; intiColCount=worksheet.UsedRange.Columns.Count; Excel.Rangerange; //负责列头Start DataColumndc; intColumnID=1; range=(Excel.Range)worksheet.Cells[1,1]; while(range.Text.ToString().Trim()!="") { dc=newDataColumn(); dc.DataType=System.Type.GetType("System.String"); dc.ColumnName=range.Text.ToString().Trim(); dt.Columns.Add(dc); range=(Excel.Range)worksheet.Cells[1,++ColumnID]; } //End for(intiRow=2;iRow<=iRowCount;iRow++) { DataRowdr=dt.NewRow(); for(intiCol=1;iCol<=iColCount;iCol++) { range=(Excel.Range)worksheet.Cells[iRow,iCol]; cellContent=(range.Value2==null)?"":range.Text.ToString(); dr[iCol-1]=cellContent; } dt.Rows.Add(dr); } wath.Stop(); TimeSpants=wath.Elapsed; //将数据读入到DataTable中——End returndt; } catch { returnnull; } finally { workbook.Close(false,oMissiong,oMissiong); System.Runtime.InteropServices.Marshal.ReleaseComObject(workbook); workbook=null; app.Workbooks.Close(); app.Quit(); System.Runtime.InteropServices.Marshal.ReleaseComObject(app); app=null; GC.Collect(); GC.WaitForPendingFinalizers(); } } ///<summary> ///使用COM,多线程读取Excel(1主线程、4副线程) ///</summary> ///<paramname="excelFilePath">路径</param> ///<returns>DataTabel</returns> publicSystem.Data.DataTableThreadReadExcel(stringexcelFilePath) { Excel.Applicationapp=newExcel.Application(); Excel.Sheetssheets=null; Excel.Workbookworkbook=null; objectoMissiong=System.Reflection.Missing.Value; System.Data.DataTabledt=newSystem.Data.DataTable(); wath.Start(); try { if(app==null) { returnnull; } workbook=app.Workbooks.Open(excelFilePath,oMissiong,oMissiong,oMissiong,oMissiong,oMissiong,oMissiong, oMissiong,oMissiong,oMissiong,oMissiong,oMissiong,oMissiong,oMissiong,oMissiong); //将数据读入到DataTable中——Start sheets=workbook.Worksheets; Excel.Worksheetworksheet=(Excel.Worksheet)sheets.get_Item(1);//读取第一张表 if(worksheet==null) returnnull; stringcellContent; intiRowCount=worksheet.UsedRange.Rows.Count; intiColCount=worksheet.UsedRange.Columns.Count; Excel.Rangerange; //负责列头Start DataColumndc; intColumnID=1; range=(Excel.Range)worksheet.Cells[1,1]; while(iColCount>=ColumnID) { dc=newDataColumn(); dc.DataType=System.Type.GetType("System.String"); stringstrNewColumnName=range.Text.ToString().Trim(); if(strNewColumnName.Length==0)strNewColumnName="_1"; //判断列名是否重复 for(inti=1;i<ColumnID;i++) { if(dt.Columns[i-1].ColumnName==strNewColumnName) strNewColumnName=strNewColumnName+"_1"; } dc.ColumnName=strNewColumnName; dt.Columns.Add(dc); range=(Excel.Range)worksheet.Cells[1,++ColumnID]; } //End //数据大于500条,使用多进程进行读取数据 if(iRowCount-1>500) { //开始多线程读取数据 //新建线程 intb2=(iRowCount-1)/10; DataTabledt1=newDataTable("dt1"); dt1=dt.Clone(); SheetOptionssheet1thread=newSheetOptions(worksheet,iColCount,2,b2+1,dt1); Threadothread1=newThread(newThreadStart(sheet1thread.SheetToDataTable)); othread1.Start(); //阻塞1毫秒,保证第一个读取dt1 Thread.Sleep(1); DataTabledt2=newDataTable("dt2"); dt2=dt.Clone(); SheetOptionssheet2thread=newSheetOptions(worksheet,iColCount,b2+2,b2*2+1,dt2); Threadothread2=newThread(newThreadStart(sheet2thread.SheetToDataTable)); othread2.Start(); DataTabledt3=newDataTable("dt3"); dt3=dt.Clone(); SheetOptionssheet3thread=newSheetOptions(worksheet,iColCount,b2*2+2,b2*3+1,dt3); Threadothread3=newThread(newThreadStart(sheet3thread.SheetToDataTable)); othread3.Start(); DataTabledt4=newDataTable("dt4"); dt4=dt.Clone(); SheetOptionssheet4thread=newSheetOptions(worksheet,iColCount,b2*3+2,b2*4+1,dt4); Threadothread4=newThread(newThreadStart(sheet4thread.SheetToDataTable)); othread4.Start(); //主线程读取剩余数据 for(intiRow=b2*4+2;iRow<=iRowCount;iRow++) { DataRowdr=dt.NewRow(); for(intiCol=1;iCol<=iColCount;iCol++) { range=(Excel.Range)worksheet.Cells[iRow,iCol]; cellContent=(range.Value2==null)?"":range.Text.ToString(); dr[iCol-1]=cellContent; } dt.Rows.Add(dr); } othread1.Join(); othread2.Join(); othread3.Join(); othread4.Join(); //将多个线程读取出来的数据追加至dt1后面 foreach(DataRowdrindt.Rows) dt1.Rows.Add(dr.ItemArray); dt.Clear(); dt.Dispose(); foreach(DataRowdrindt2.Rows) dt1.Rows.Add(dr.ItemArray); dt2.Clear(); dt2.Dispose(); foreach(DataRowdrindt3.Rows) dt1.Rows.Add(dr.ItemArray); dt3.Clear(); dt3.Dispose(); foreach(DataRowdrindt4.Rows) dt1.Rows.Add(dr.ItemArray); dt4.Clear(); dt4.Dispose(); returndt1; } else { for(intiRow=2;iRow<=iRowCount;iRow++) { DataRowdr=dt.NewRow(); for(intiCol=1;iCol<=iColCount;iCol++) { range=(Excel.Range)worksheet.Cells[iRow,iCol]; cellContent=(range.Value2==null)?"":range.Text.ToString(); dr[iCol-1]=cellContent; } dt.Rows.Add(dr); } } wath.Stop(); TimeSpants=wath.Elapsed; //将数据读入到DataTable中——End returndt; } catch { returnnull; } finally { workbook.Close(false,oMissiong,oMissiong); System.Runtime.InteropServices.Marshal.ReleaseComObject(workbook); System.Runtime.InteropServices.Marshal.ReleaseComObject(sheets); workbook=null; app.Workbooks.Close(); app.Quit(); System.Runtime.InteropServices.Marshal.ReleaseComObject(app); app=null; GC.Collect(); GC.WaitForPendingFinalizers(); } }
(3)NPOI方式读取Excel(此方法未经过测试)
NPOI是POI项目的.NET版本。POI是一个开源的Java读写Excel、WORD等微软OLE2组件文档的项目。使用NPOI你就可以在没有安装Office或者相应环境的机器上对WORD/EXCEL文档进行读写。
优点:读取Excel速度较快,读取方式操作灵活性
缺点:需要下载相应的插件并添加到系统引用当中。
///<summary> ///将excel中的数据导入到DataTable中 ///</summary> ///<paramname="sheetName">excel工作薄sheet的名称</param> ///<paramname="isFirstRowColumn">第一行是否是DataTable的列名</param> ///<returns>返回的DataTable</returns> publicDataTableExcelToDataTable(stringsheetName,boolisFirstRowColumn) { ISheetsheet=null; DataTabledata=newDataTable(); intstartRow=0; try { fs=newFileStream(fileName,FileMode.Open,FileAccess.Read); if(fileName.IndexOf(".xlsx")>0)//2007版本 workbook=newXSSFWorkbook(fs); elseif(fileName.IndexOf(".xls")>0)//2003版本 workbook=newHSSFWorkbook(fs); if(sheetName!=null) { sheet=workbook.GetSheet(sheetName); } else { sheet=workbook.GetSheetAt(0); } if(sheet!=null) { IRowfirstRow=sheet.GetRow(0); intcellCount=firstRow.LastCellNum;//一行最后一个cell的编号即总的列数 if(isFirstRowColumn) { for(inti=firstRow.FirstCellNum;i<cellCount;++i) { DataColumncolumn=newDataColumn(firstRow.GetCell(i).StringCellValue); data.Columns.Add(column); } startRow=sheet.FirstRowNum+1; } else { startRow=sheet.FirstRowNum; } //最后一列的标号 introwCount=sheet.LastRowNum; for(inti=startRow;i<=rowCount;++i) { IRowrow=sheet.GetRow(i); if(row==null)continue;//没有数据的行默认是null DataRowdataRow=data.NewRow(); for(intj=row.FirstCellNum;j<cellCount;++j) { if(row.GetCell(j)!=null)//同理,没有数据的单元格都默认是null dataRow[j]=row.GetCell(j).ToString(); } data.Rows.Add(dataRow); } } returndata; } catch(Exceptionex) { Console.WriteLine("Exception:"+ex.Message); returnnull; } }
下面是一些相关的文章,大家可以参考下