首页 后端开发 C#.Net教程 C#读取Excel的三种方式以及比较分析

C#读取Excel的三种方式以及比较分析

Jan 13, 2017 pm 05:10 PM

(1)OleDB方式

优点:将Excel直接当做数据源处理,通过SQL直接读取内容,读取速度较快。

缺点:读取数据方式不够灵活,无法直接读取某一个单元格,只有将整个Sheet页读取出来后(结果为Datatable)再在Datatable中根据行列数来获取指定的值。

当Excel数据量很大时。会非常占用内存,当内存不够时会抛出内存溢出的异常。

读取代码如下:

 
public DataTable GetExcelTableByOleDB(string strExcelPath, string tableName)
{
  try
  {
    DataTable dtExcel = new DataTable();
    //数据表
    DataSet ds = new DataSet();
    //获取文件扩展名
    string strExtension = System.IO.Path.GetExtension(strExcelPath);
    string strFileName = System.IO.Path.GetFileName(strExcelPath);
    //Excel的连接
    OleDbConnection objConn = null;
    switch (strExtension)
    {
      case ".xls":
        objConn = new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + strExcelPath + ";" + "Extended Properties=\"Excel 8.0;HDR=NO;IMEX=1;\"");
        break;
      case ".xlsx":
        objConn = new OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + strExcelPath + ";" + "Extended Properties=\"Excel 12.0;HDR=NO;IMEX=1;\"");
        break;
      default:
        objConn = null;
        break;
    }
    if (objConn == null)
    {
      return null;
    }
    objConn.Open();
    //获取Excel中所有Sheet表的信息
    //System.Data.DataTable schemaTable = objConn.GetOleDbSchemaTable(System.Data.OleDb.OleDbSchemaGuid.Tables, null);
    //获取Excel的第一个Sheet表名
    //string tableName = schemaTable.Rows[0][2].ToString().Trim();
    string strSql = "select * from [" + tableName + "]";
    //获取Excel指定Sheet表中的信息
    OleDbCommand objCmd = new OleDbCommand(strSql, objConn);
    OleDbDataAdapter myData = new OleDbDataAdapter(strSql, objConn);
    myData.Fill(ds, tableName);//填充数据
    objConn.Close();
    //dtExcel即为excel文件中指定表中存储的信息
    dtExcel = ds.Tables[tableName];
    return dtExcel;
  }
  catch
  {
    return null;
  }
}
登录后复制

下面说明一下连接字符串

HDR=Yes,这代表第一行是标题,不做为数据使用(但是我在实际使用中,如果第一行存在复杂数值,那么读取得到的Datatable列标题会自动设置为F1、F2等方式命名,与实际应用不符,所以当时是通过HDR=No方式将所有内容读取到Datatable中,然后手动将第一行设置成标题的);IMEX ( IMport EXport mode )设置
IMEX 有三种模式:
0 is Export mode
1 is Import mode
2 is Linked mode (full update capabilities)
我这里特别要说明的就是 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然后将原Sheet Copy进去。但实际情况并不能为每个Excel做以上检查。下面给出了过滤的方案。(此问题我们有验证过,大家自己验证一下吧)

//objConn为读取Excel的链接,下面通过过滤来获取有效的Sheet页名称集合
 System.Data.DataTable schemaTable = objConn.GetOleDbSchemaTable(System.Data.OleDb.OleDbSchemaGuid.Tables, null);
 List<string> lstSheetNames = new List<string>();
 for (int i = 0; i < schemaTable.Rows.Count; i++)
 {
   string strSheetName = (string)dtSheetName.Rows[i]["TABLE_NAME"];
   if (strSheetName.Contains("$") && !strSheetName.Replace("&#39;", "").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权限。

读取代码如下:

private Stopwatch wath = new Stopwatch();
/// <summary>
/// 使用COM读取Excel
/// </summary>
/// <param name="excelFilePath">路径</param>
/// <returns>DataTabel</returns>
public System.Data.DataTable GetExcelData(string excelFilePath)
{
  Excel.Application app = new Excel.Application();
  Excel.Sheets sheets;
  Excel.Workbook workbook = null;
  object oMissiong = System.Reflection.Missing.Value;
  System.Data.DataTable dt = new System.Data.DataTable();
  wath.Start();
  try
  {
    if (app == null)
    {
      return null;
    }
    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.Worksheet worksheet = (Excel.Worksheet)sheets.get_Item(1);//读取第一张表
    if (worksheet == null)
      return null;
    string cellContent;
    int iRowCount = worksheet.UsedRange.Rows.Count;
    int iColCount = worksheet.UsedRange.Columns.Count;
    Excel.Range range;
    //负责列头Start
    DataColumn dc;
    int ColumnID = 1;
    range = (Excel.Range)worksheet.Cells[1, 1];
    while (range.Text.ToString().Trim() != "")
    {
      dc = new DataColumn();
      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 (int iRow = 2; iRow <= iRowCount; iRow++)
    {
      DataRow dr = dt.NewRow();
      for (int iCol = 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();
    TimeSpan ts = wath.Elapsed;
    //将数据读入到DataTable中——End
    return dt;
  }
  catch
  {
    return null;
  }
  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>
/// <param name="excelFilePath">路径</param>
/// <returns>DataTabel</returns>
public System.Data.DataTable ThreadReadExcel(string excelFilePath)
{
  Excel.Application app = new Excel.Application();
  Excel.Sheets sheets = null;
  Excel.Workbook workbook = null;
  object oMissiong = System.Reflection.Missing.Value;
  System.Data.DataTable dt = new System.Data.DataTable();
  wath.Start();
  try
  {
    if (app == null)
    {
      return null;
    }
    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.Worksheet worksheet = (Excel.Worksheet)sheets.get_Item(1);//读取第一张表
    if (worksheet == null)
      return null;
    string cellContent;
    int iRowCount = worksheet.UsedRange.Rows.Count;
    int iColCount = worksheet.UsedRange.Columns.Count;
    Excel.Range range;
    //负责列头Start
    DataColumn dc;
    int ColumnID = 1;
    range = (Excel.Range)worksheet.Cells[1, 1];
    while (iColCount >= ColumnID)
    {
      dc = new DataColumn();
      dc.DataType = System.Type.GetType("System.String");
      string strNewColumnName = range.Text.ToString().Trim();
      if (strNewColumnName.Length == 0) strNewColumnName = "_1";
      //判断列名是否重复
      for (int i = 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)
    {
      //开始多线程读取数据
      //新建线程
      int b2 = (iRowCount - 1) / 10;
      DataTable dt1 = new DataTable("dt1");
      dt1 = dt.Clone();
      SheetOptions sheet1thread = new SheetOptions(worksheet, iColCount, 2, b2 + 1, dt1);
      Thread othread1 = new Thread(new ThreadStart(sheet1thread.SheetToDataTable));
      othread1.Start();
      //阻塞 1 毫秒,保证第一个读取 dt1
      Thread.Sleep(1);
      DataTable dt2 = new DataTable("dt2");
      dt2 = dt.Clone();
      SheetOptions sheet2thread = new SheetOptions(worksheet, iColCount, b2 + 2, b2 * 2 + 1, dt2);
      Thread othread2 = new Thread(new ThreadStart(sheet2thread.SheetToDataTable));
      othread2.Start();
      DataTable dt3 = new DataTable("dt3");
      dt3 = dt.Clone();
      SheetOptions sheet3thread = new SheetOptions(worksheet, iColCount, b2 * 2 + 2, b2 * 3 + 1, dt3);
      Thread othread3 = new Thread(new ThreadStart(sheet3thread.SheetToDataTable));
      othread3.Start();
      DataTable dt4 = new DataTable("dt4");
      dt4 = dt.Clone();
      SheetOptions sheet4thread = new SheetOptions(worksheet, iColCount, b2 * 3 + 2, b2 * 4 + 1, dt4);
      Thread othread4 = new Thread(new ThreadStart(sheet4thread.SheetToDataTable));
      othread4.Start();
      //主线程读取剩余数据
      for (int iRow = b2 * 4 + 2; iRow <= iRowCount; iRow++)
      {
        DataRow dr = dt.NewRow();
        for (int iCol = 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 (DataRow dr in dt.Rows)
        dt1.Rows.Add(dr.ItemArray);
      dt.Clear();
      dt.Dispose();
      foreach (DataRow dr in dt2.Rows)
        dt1.Rows.Add(dr.ItemArray);
      dt2.Clear();
      dt2.Dispose();
      foreach (DataRow dr in dt3.Rows)
        dt1.Rows.Add(dr.ItemArray);
      dt3.Clear();
      dt3.Dispose();
      foreach (DataRow dr in dt4.Rows)
        dt1.Rows.Add(dr.ItemArray);
      dt4.Clear();
      dt4.Dispose();
      return dt1;
    }
    else
    {
      for (int iRow = 2; iRow <= iRowCount; iRow++)
      {
        DataRow dr = dt.NewRow();
        for (int iCol = 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();
    TimeSpan ts = wath.Elapsed;
    //将数据读入到DataTable中——End
    return dt;
  }
  catch
  {
    return null;
  }
  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>
/// <param name="sheetName">excel工作薄sheet的名称</param>
/// <param name="isFirstRowColumn">第一行是否是DataTable的列名</param>
/// <returns>返回的DataTable</returns>
public DataTable ExcelToDataTable(string sheetName, bool isFirstRowColumn)
{
  ISheet sheet = null;
  DataTable data = new DataTable();
  int startRow = 0;
  try
  {
    fs = new FileStream(fileName, FileMode.Open, FileAccess.Read);
    if (fileName.IndexOf(".xlsx") > 0) // 2007版本
      workbook = new XSSFWorkbook(fs);
    else if (fileName.IndexOf(".xls") > 0) // 2003版本
      workbook = new HSSFWorkbook(fs);
    if (sheetName != null)
    {
      sheet = workbook.GetSheet(sheetName);
    }
    else
    {
      sheet = workbook.GetSheetAt(0);
    }
    if (sheet != null)
    {
      IRow firstRow = sheet.GetRow(0);
      int cellCount = firstRow.LastCellNum; //一行最后一个cell的编号 即总的列数
      if (isFirstRowColumn)
      {
        for (int i = firstRow.FirstCellNum; i < cellCount; ++i)
        {
          DataColumn column = new DataColumn(firstRow.GetCell(i).StringCellValue);
          data.Columns.Add(column);
        }
        startRow = sheet.FirstRowNum + 1;
      }
      else
      {
        startRow = sheet.FirstRowNum;
      }
      //最后一列的标号
      int rowCount = sheet.LastRowNum;
      for (int i = startRow; i <= rowCount; ++i)
      {
        IRow row = sheet.GetRow(i);
        if (row == null) continue; //没有数据的行默认是null       
         
        DataRow dataRow = data.NewRow();
        for (int j = row.FirstCellNum; j < cellCount; ++j)
        {
          if (row.GetCell(j) != null) //同理,没有数据的单元格都默认是null
            dataRow[j] = row.GetCell(j).ToString();
        }
        data.Rows.Add(dataRow);
      }
    }
    return data;
  }
  catch (Exception ex)
  {
    Console.WriteLine("Exception: " + ex.Message);
    return null;
  }
}
登录后复制

更多C#读取Excel的三种方式以及比较分析相关文章请关注PHP中文网!


本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

char在C语言字符串中的作用是什么 char在C语言字符串中的作用是什么 Apr 03, 2025 pm 03:15 PM

在 C 语言中,char 类型在字符串中用于:1. 存储单个字符;2. 使用数组表示字符串并以 null 终止符结束;3. 通过字符串操作函数进行操作;4. 从键盘读取或输出字符串。

C语言各种符号的使用方法 C语言各种符号的使用方法 Apr 03, 2025 pm 04:48 PM

C 语言中符号的使用方法涵盖算术、赋值、条件、逻辑、位运算符等。算术运算符用于基本数学运算,赋值运算符用于赋值和加减乘除赋值,条件运算符用于根据条件执行不同操作,逻辑运算符用于逻辑操作,位运算符用于位级操作,特殊常量用于表示空指针、文件结束标记和非数字值。

char在C语言中如何处理特殊字符 char在C语言中如何处理特殊字符 Apr 03, 2025 pm 03:18 PM

C语言中通过转义序列处理特殊字符,如:\n表示换行符。\t表示制表符。使用转义序列或字符常量表示特殊字符,如char c = '\n'。注意,反斜杠需要转义两次。不同平台和编译器可能有不同的转义序列,请查阅文档。

c#多线程和异步的区别 c#多线程和异步的区别 Apr 03, 2025 pm 02:57 PM

多线程和异步的区别在于,多线程同时执行多个线程,而异步在不阻塞当前线程的情况下执行操作。多线程用于计算密集型任务,而异步用于用户交互操作。多线程的优势是提高计算性能,异步的优势是不阻塞 UI 线程。选择多线程还是异步取决于任务性质:计算密集型任务使用多线程,与外部资源交互且需要保持 UI 响应的任务使用异步。

char与wchar_t在C语言中的区别 char与wchar_t在C语言中的区别 Apr 03, 2025 pm 03:09 PM

在 C 语言中,char 和 wchar_t 的主要区别在于字符编码:char 使用 ASCII 或扩展 ASCII,wchar_t 使用 Unicode;char 占用 1-2 个字节,wchar_t 占用 2-4 个字节;char 适用于英语文本,wchar_t 适用于多语言文本;char 广泛支持,wchar_t 依赖于编译器和操作系统是否支持 Unicode;char 的字符范围受限,wchar_t 的字符范围更大,并使用专门的函数进行算术运算。

char在C语言中如何进行类型转换 char在C语言中如何进行类型转换 Apr 03, 2025 pm 03:21 PM

在 C 语言中,char 类型转换可以通过:强制类型转换:使用强制类型转换符将一种类型的数据直接转换为另一种类型。自动类型转换:当一种类型的数据可以容纳另一种类型的值时,编译器自动进行转换。

char和unsigned char的区别是什么 char和unsigned char的区别是什么 Apr 03, 2025 pm 03:36 PM

char 和 unsigned char 是存储字符数据的两种数据类型,主要区别在于处理负数和正数的方式:值范围:char 有符号 (-128 到 127),unsigned char 无符号 (0 到 255)。负数处理:char 可以存储负数,unsigned char 不能。位模式:char 最高位表示符号,unsigned char 无符号位。算术运算:char 和 unsigned char 作为有符号和无符号类型,其算术运算方式不同。兼容性:char 和 unsigned char

char数组在C语言中如何使用 char数组在C语言中如何使用 Apr 03, 2025 pm 03:24 PM

char 数组在 C 语言中存储字符序列,声明为 char array_name[size]。访问元素通过下标运算符,元素以空终止符 '\0' 结尾,用于表示字符串终点。C 语言提供多种字符串操作函数,如 strlen()、strcpy()、strcat() 和 strcmp()。

See all articles