解决 C#、Java、VB、Delphi、VC 等语言中读写 Excel 文件太慢的问题

Posted by NosaLee in .NET Programming, Common Programming on 08-04-2012. Tags:

作者:牧山道人
原文地址:http://www.seeksunslowly.com/operation-excel-xlsx-slowlw-sc
转载请注明出处,谢谢。
_____________________________________

最近,在 .NET 中尝试从 Excel 导入数据到 Detail 样式的 ListView 中,记录数在 3,000 左右,发现导入非常缓慢(将近 10 分钟),别说用户不能忍受,自己都无法接受,但这是必需的功能,不能去掉,所以必须想办法解决。

其中,读取 Excel 文档数据的方式为:逐行逐单元格读取,关键代码如下:

1
2
3
4
5
6
7
......
For r% = 1 To rows ' 外回圈,回圈于 rows,r = row。
   For c% = 0 To 9   ' 内回圈,回圈于 columns,c = column。
      fields(c) = sheet.Cells(r, c + 1).Text
   Next
Next
......

通过在网上搜索,大部分说法是把 Excel 文件作为数据源,然后使用 ADO.NET 组件读取,这样会快很多。
这个方法我没去测试,因为这个解决方法也不能接受,整个软件没有一处用到数据库及相关组件,为了这个导入功能增加额外的组件,是我不能接受的,必须还得用 Excel 相关对象完成。

现在,只能自己摸索解决了,通过不断在 Excel 中录制宏,然后分析代码,发现了一种不用遍历所有单元格即可取得数据的方法。
感觉这个方法应该能大幅提高效率,因为除去与 Excel 应用、工作簿本身的交互,一个 sheet 我只需要访问一次就能取到所有数据。
经过测试,方法是可行的,效率是可观的。

其核心是 sheet.Range(“A1:X#”).Value 的使用。
sheet 对象的 Range 方法会返回指定区域所有数据:Object 类型的二维数组,第一维表示行,第二维表示列,各维下标都从 1 开始(Excel 内定的下标,跟其他一般编程语言不太一样)。
有了这个方法,一切变得如此简单,下面给出完整可用的 VB 2008 代码,细节(比如列数)请自行调整。
注意:这个方法不仅适用于 VB 2008,其他如 C#、Delphi、Java、VB、VC、PB 等语言同样适用(因为都可以通过对象操作 Excel)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
Private Sub ImportFromExcel(ByVal fn$)

   ' 从 Excel 文档读取数据,fn - Excel 档名。

   On Error Resume Next
   
   Dim excel As Object                 ' Excel application.
   Dim wb As Object                    ' Excel workbook.
   Dim sheet As Object                 ' Workbook sheet.
   Dim rows%                           ' 当前 Sheet 有效行数。
   Dim eData(,) As Object              ' 用于撷取 Excel 资料。注意:必须为二维 Object 型别数组(各维下标均自 1 始)。

   
   excel = CreateObject("Excel.Application")
   If excel Is Nothing Then
      MsgBox("打开 Excel 应用失败,请确定您已正确安装 Excel。", _
             MsgBoxStyle.Exclamation)
      Return
   End If

   wb = excel.Workbooks.Open(fn, , True) ' 唯读开启.

   If wb Is Nothing Then GoTo CLS ' 未通过验证或用户取消(可能需要输入密码或出现其他对话框)。    
   sheet = wb.Worksheets(1)
   rows = sheet.UsedRange.Rows.Count   ' 取得当前 sheet 有效行数。

   ' 通过 sheet 对象的 Range 方法一次性读取整个 sheet 数据。
   eData = sheet.Range("A1:C" & rows.ToString).Value

   '' 以下代码简单打印读取到的数据。您需要将其转换为自己所开发产品的具体业务代码。
   For i% = 1 To rows
      For j% = 1 To 3
         Debug.Print(eData(i, j)
      Next
   Next
   ''

CLS:
   wb.Close()        ' 关闭工作簿。
   excel.Quit()      ' 退出 Excel。    

End Sub

最后,再补充一点,Range 方法也可用于 Excel 档案的写入。具体代码不再完整给出,核心部分如下:

1
2
3
4
Dim eData(1 To 100, 1 To 3) As Object ' 用于写入 Excel 资料。注意:必须为二维 Object 型别数组(各维下标均自 1 始)。
...... ' 准备好 eData
sheet.Range("A1:C100").Value = eData
...... ' 善后事宜。

Post a comment