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;
}
}
下面是一些相关的文章,大家可以参考下