.NET操作Excel:高效材料读写与批量运行

news/2025/9/29 10:51:57/文章来源:https://www.cnblogs.com/lxjshuju/p/19118311

在前六篇文章中,我们系统地学习了Excel自动化开发的基础知识和高级技巧,包括开发环境搭建、Excel对象模型理解、工作簿和工作表操作、单元格范围的基本和高级操作等核心内容。现在,让我们深入探讨一个在实际开发中非常重要的主题——高效数据读写与批量操作。

在处理大量数据时,性能是一个关键考量因素。许多开发者在刚开始使用Excel自动化时,会采用逐个单元格读写的方式,这种方式在处理小量数据时表现尚可,但面对大量数据时会出现严重的性能问题。掌握高效的批量操作技巧,可以让我们在处理成千上万条数据时依然保持良好的性能表现。

开源项目网址:Gitee OfficeInterop
项目官方网址:MudTools OfficeInterop

理解高效数据操作的重要性

在Excel自动化开发中,高效的数据操作能够帮助我们:

  1. 显著提升处理速度 - 通过批量操作将处理时间从几分钟缩短到几秒钟
  2. 降低系统资源消耗 - 减少COM调用次数,降低内存和CPU使用率
  3. 改善用户体验 - 快速响应让用户感受到流畅的操作体验
  4. 扩展应用处理能力 - 支持处理更大规模的数据集

典型应用场景

场景:大数据量导出

在实际业务中,我们经常需要将大量数据从数据库导出到Excel中。例如,一个销售系统可能需要导出数万条销售记录,如果使用循环单个单元格的方式可能需要几分钟,而使用数组批量操作只需几秒钟。

场景2:复杂数据处理

在进行复杂的数据分析和处理时,需要在C#中对大量Excel数据进行计算和转换,然后再写回Excel。

场景3:批量数据导入

从Excel中批量读取数据并导入到数据库或其他系统中,要求快速高效地完成数据提取。

场景4:报表批量生成

根据模板批量生成大量报表,需要快速填充数据并保存为独立的文件。

性能瓶颈分析

为什么循环操作单个单元格很慢?

在Excel自动化开发中,最常见的性能问题是使用循环逐个操作单元格。这种方式存在以下几个性能瓶颈:

1. COM调用开销

每次访问Excel对象模型都需要进行COM调用,这会带来显著的性能开销:

// 低效的方式:逐个单元格操作
for (int i = 1; i <= 10000; i++)
{
// 每次调用都会产生COM开销
worksheet.Cells[i, 1].Value = i;
worksheet.Cells[i, 2].Value = "数据" + i;
}
2. 频繁的上下文切换

每次COM调用都会在.NET和COM之间进行上下文切换,这种切换会消耗大量时间。

3. Excel内部处理开销

Excel在每次单元格操作后可能需要进行内部状态更新和计算,进一步增加了处理时间。

"一次读写"原则

为了避免上述性能问题,我们应该遵循"一次读写"原则:将整个区域读取到C#二维数组中处理,再一次性写回Excel。

1. 批量读取数据

使用ArrayValue属性可以一次性读取整个区域的数据:

// 高效的方式:批量读取数据
var dataRange = worksheet.Range("A1:Z1000");
object[,] dataArray = dataRange.ArrayValue;
// 在内存中处理数据
for (int row = 1; row <= dataArray.GetLength(0); row++)
{
for (int col = 1; col <= dataArray.GetLength(1); col++)
{
// 处理数据
if (dataArray[row, col] != null)
{
dataArray[row, col] = dataArray[row, col].ToString().ToUpper();
}
}
}
// 一次性写回Excel
dataRange.ArrayValue = dataArray;

2. 批量写入数据

同样地,我们可以批量创建数据并一次性写入Excel:

// 创建大数据数组
int rowCount = 50000;
int colCount = 10;
object[,] data = new object[rowCount, colCount];
// 在内存中填充数据
for (int row = 0; row < rowCount; row++)
{
for (int col = 0; col < colCount; col++)
{
data[row, col] = $"数据{row}-{col}";
}
}
// 一次性写入Excel
worksheet.Range("A1").Resize(rowCount, colCount).ArrayValue = data;

实战案例:大数据量导出优化

让我们通过一个完整的示例来演示如何优化大数据量导出操作。我们将比较传统逐个单元格操作和高效批量操作的性能差异:

using MudTools.OfficeInterop;
using System;
using System.Diagnostics;
namespace ExcelHighPerformanceExportDemo
{
class Program
{
static void Main(string[] args)
{
try
{
// 比较两种导出方式的性能
CompareExportPerformance();
}
catch (Exception ex)
{
Console.WriteLine($"操作失败: {ex.Message}");
}
}
static void CompareExportPerformance()
{
const int rowCount = 10000;
const int colCount = 5;
Console.WriteLine($"准备导出 {rowCount}{colCount} 列的数据...");
// 方法1:逐个单元格操作(低效)
ExportUsingCellByCell(rowCount, colCount);
// 方法2:批量数组操作(高效)
ExportUsingBatchArray(rowCount, colCount);
}
static void ExportUsingCellByCell(int rowCount, int colCount)
{
Console.WriteLine("\n=== 方法1:逐个单元格操作 ===");
var stopwatch = Stopwatch.StartNew();
// 创建Excel应用程序实例
using var excelApp = ExcelFactory.BlankWorkbook();
excelApp.Visible = false;
excelApp.DisplayAlerts = false;
// 获取活动工作簿和工作表
var workbook = excelApp.ActiveWorkbook;
var worksheet = workbook.ActiveSheetWrap;
// 逐个单元格写入数据
for (int row = 1; row <= rowCount; row++)
{
for (int col = 1; col <= colCount; col++)
{
if (col == 1)
worksheet.Cells[row, col].Value = row;
else if (col == 2)
worksheet.Cells[row, col].Value = $"姓名{row}";
else if (col == 3)
worksheet.Cells[row, col].Value = $"部门{row % 10}";
else if (col == 4)
worksheet.Cells[row, col].Value = 3000 + (row % 100) * 100;
else
worksheet.Cells[row, col].Value = DateTime.Now.AddDays(row % 365);
}
}
stopwatch.Stop();
Console.WriteLine($"逐个单元格操作耗时: {stopwatch.ElapsedMilliseconds} 毫秒");
// 保存文件
string fileName = $"逐个单元格导出_{DateTime.Now:yyyyMMdd_HHmmss}.xlsx";
workbook.SaveAs(fileName);
Console.WriteLine($"文件已保存: {fileName}");
}
static void ExportUsingBatchArray(int rowCount, int colCount)
{
Console.WriteLine("\n=== 方法2:批量数组操作 ===");
var stopwatch = Stopwatch.StartNew();
// 创建Excel应用程序实例
using var excelApp = ExcelFactory.BlankWorkbook();
excelApp.Visible = false;
excelApp.DisplayAlerts = false;
// 获取活动工作簿和工作表
var workbook = excelApp.ActiveWorkbook;
var worksheet = workbook.ActiveSheetWrap;
// 创建数据数组
object[,] data = new object[rowCount, colCount];
// 在内存中填充数据
for (int row = 0; row < rowCount; row++)
{
for (int col = 0; col < colCount; col++)
{
if (col == 0)
data[row, col] = row + 1;
else if (col == 1)
data[row, col] = $"姓名{row + 1}";
else if (col == 2)
data[row, col] = $"部门{(row + 1) % 10}";
else if (col == 3)
data[row, col] = 3000 + ((row + 1) % 100) * 100;
else
data[row, col] = DateTime.Now.AddDays((row + 1) % 365);
}
}
// 一次性写入Excel
worksheet.Range("A1").Resize(rowCount, colCount).ArrayValue = data;
stopwatch.Stop();
Console.WriteLine($"批量数组操作耗时: {stopwatch.ElapsedMilliseconds} 毫秒");
// 保存文件
string fileName = $"批量数组导出_{DateTime.Now:yyyyMMdd_HHmmss}.xlsx";
workbook.SaveAs(fileName);
Console.WriteLine($"文件已保存: {fileName}");
// 显示性能对比
Console.WriteLine($"\n性能对比:");
Console.WriteLine($"批量操作比逐个单元格操作快 {stopwatch.ElapsedMilliseconds / 1000.0:F2} 倍");
}
}
}

实战案例:复杂数据处理与转换

在实际业务中,我们经常需要对Excel中的数据进行复杂处理和转换。以下示例演示如何高效地处理大量数据:

using MudTools.OfficeInterop;
using System;
namespace ExcelDataProcessingDemo
{
class Program
{
static void Main(string[] args)
{
try
{
ProcessSalesData();
}
catch (Exception ex)
{
Console.WriteLine($"操作失败: {ex.Message}");
}
}
static void ProcessSalesData()
{
// 创建Excel应用程序实例
using var excelApp = ExcelFactory.BlankWorkbook();
excelApp.Visible = false;
excelApp.DisplayAlerts = false;
// 获取活动工作簿和工作表
var workbook = excelApp.ActiveWorkbook;
var worksheet = workbook.ActiveSheetWrap;
// 模拟导入原始销售数据
CreateSalesData(worksheet);
// 高效处理数据
ProcessSalesDataEfficiently(worksheet);
// 保存结果
workbook.SaveAs("处理后的销售数据.xlsx");
Console.WriteLine("销售数据处理完成!");
}
static void CreateSalesData(IExcelWorksheet worksheet)
{
// 创建表头
worksheet.Range("A1").Value = "销售员";
worksheet.Range("B1").Value = "产品";
worksheet.Range("C1").Value = "数量";
worksheet.Range("D1").Value = "单价";
worksheet.Range("E1").Value = "销售额";
worksheet.Range("F1").Value = "销售日期";
// 设置表头格式
var headerRange = worksheet.Range("A1:F1");
headerRange.Font.Bold = true;
headerRange.Interior.Color = System.Drawing.Color.LightBlue;
// 创建大量销售数据
int rowCount = 20000;
object[,] salesData = new object[rowCount, 6];
Random random = new Random();
string[] salespeople = { "张三", "李四", "王五", "赵六", "钱七" };
string[] products = { "产品A", "产品B", "产品C", "产品D", "产品E" };
for (int i = 0; i < rowCount; i++)
{
salesData[i, 0] = salespeople[random.Next(salespeople.Length)];
salesData[i, 1] = products[random.Next(products.Length)];
salesData[i, 2] = random.Next(1, 100);
salesData[i, 3] = random.Next(100, 1000);
salesData[i, 4] = (int)salesData[i, 2] * (int)salesData[i, 3]; // 销售额=数量*单价
salesData[i, 5] = DateTime.Now.AddDays(-random.Next(365));
}
// 批量写入数据
worksheet.Range["A2"].Resize(rowCount, 6).ArrayValue = salesData;
}
static void ProcessSalesDataEfficiently(IExcelWorksheet worksheet)
{
Console.WriteLine("开始处理销售数据...");
// 获取数据区域(排除表头)
var dataRange = worksheet.Range["A2:F20001"];
// 批量读取数据到数组
object[,] dataArray = dataRange.ArrayValue;
// 在内存中处理数据
int rowCount = dataArray.GetLength(0);
int colCount = dataArray.GetLength(1);
// 添加额外的计算列:利润(假设利润率为20%)
object[,] processedData = new object[rowCount, colCount + 1];
for (int row = 0; row < rowCount; row++)
{
// 复制原始数据
for (int col = 0; col < colCount; col++)
{
processedData[row, col] = dataArray[row, col];
}
// 计算利润(销售额的20%)
if (dataArray[row, 4] != null && double.TryParse(dataArray[row, 4].ToString(), out double sales))
{
processedData[row, colCount] = sales * 0.2; // 利润
}
else
{
processedData[row, colCount] = 0;
}
}
// 扩展Excel区域以容纳新列
var extendedRange = worksheet.Range("A2").Resize(rowCount, colCount + 1);
extendedRange.ArrayValue = processedData;
// 添加新列的表头
worksheet.Range("G1").Value = "利润";
worksheet.Range("G1").Font.Bold = true;
worksheet.Range("G1").Interior.Color = System.Drawing.Color.LightBlue;
// 设置数字格式
worksheet.Range("C2:D20001").NumberFormat = "#,##0";
worksheet.Range("E2:G20001").NumberFormat = "#,##0.00";
// 自动调整列宽
worksheet.Columns.AutoFit();
Console.WriteLine("销售数据处理完成!");
}
}
}

高效数据操作的重要属性和方法详解

核心属性

性能优化技巧

  1. 最小化COM调用:尽可能使用批量操作而不是循环单个单元格
  2. 合理使用内存:处理超大数据集时注意内存使用情况
  3. 适时释放资源:使用using语句确保及时释放COM对象

最佳实践和注意事项

1. 选择合适的数据操作方式

根据数据量大小选择合适的方式:

// 小量数据(< 1000单元格)可以使用逐个单元格操作
if (cellCount < 1000)
{
for (int i = 1; i <= cellCount; i++)
{
worksheet.Cells[i, 1].Value = i;
}
}
// 大量数据应使用批量操作
else
{
object[,] data = new object[cellCount, 1];
for (int i = 0; i < cellCount; i++)
{
data[i, 0] = i + 1;
}
worksheet.Range("A1").Resize(cellCount, 1).ArrayValue = data;
}

2. 内存管理

处理大数据集时要注意内存使用:

// 分批处理超大数据集
const int batchSize = 10000;
int totalRows = 100000;
for (int batch = 0; batch < totalRows; batch += batchSize)
{
int currentBatchSize = Math.Min(batchSize, totalRows - batch);
object[,] batchData = new object[currentBatchSize, colCount];
// 处理当前批次数据
// ...
// 写入Excel
worksheet.Range("A1").Offset(batch, 0).Resize(currentBatchSize, colCount).ArrayValue = batchData;
}

3. 异常处理

批量操作可能涉及大量数据,需要妥善处理异常:

try
{
worksheet.Range("A1").Resize(rowCount, colCount).ArrayValue = data;
}
catch (OutOfMemoryException ex)
{
Console.WriteLine("内存不足,请减少数据量或分批处理");
}
catch (System.Runtime.InteropServices.COMException ex)
{
Console.WriteLine($"COM操作失败: {ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"操作失败: {ex.Message}");
}

4. 性能监控

在关键操作中添加性能监控:

var stopwatch = Stopwatch.StartNew();
worksheet.Range("A1").Resize(rowCount, colCount).ArrayValue = data;
stopwatch.Stop();
Console.WriteLine($"数据写入耗时: {stopwatch.ElapsedMilliseconds} 毫秒");

总结

通过本文的学习,我们掌握了以下关键知识点:

  1. 性能瓶颈分析 - 理解了逐个单元格操作慢的原因,主要是COM调用开销和上下文切换
  2. "一次读写"原则 - 学会了使用ArrayValue属性进行批量数据读写操作
  3. 实际应用场景 - 通过大数据量导出和复杂数据处理案例,看到了高效操作在实际业务中的应用
  4. 性能优化技巧 - 掌握了最小化COM调用、合理使用内存等优化方法
  5. 最佳实践 - 了解了数据操作方式选择、内存管理、异常处理等关键注意事项

通过采用批量操作方式,我们可以将处理成千上万条数据的时间从几分钟缩短到几秒钟,极大地提升了Excel自动化应用的性能和用户体验。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/921711.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【深度解析】从零构建体育数据流水线:足球与篮球数据接入实战

【深度解析】从零构建体育数据流水线:足球与篮球数据接入实战不止于兴趣,更是技术实践 作为一名开发者兼体育爱好者,我始终对数据驱动体育的世界着迷。但不同于普通观众,我们更关心的是:这些数据如何通过技术手段…

Qwen-Image技术报告

原文:https://mp.weixin.qq.com/s/GLEa3fIc67uX9IK50LDeNw 全文摘要本文介绍了一种名为Qwen-Image的图像生成基础模型,它在复杂文本渲染和精确图像编辑方面取得了显著进展。为了解决复杂文本渲染的挑战,作者设计了一…

苏州相城区网站建设渭南市工程建设项目审批网上办事大厅

✅作者简介&#xff1a;人工智能专业本科在读&#xff0c;喜欢计算机与编程&#xff0c;写博客记录自己的学习历程。 &#x1f34e;个人主页&#xff1a;小嗷犬的个人主页 &#x1f34a;个人网站&#xff1a;小嗷犬的技术小站 &#x1f96d;个人信条&#xff1a;为天地立心&…

wordpress全站ajax用wordpress仿a站

中介者模式 中介者模式 中介者模式 介绍&#xff1a;用一个中介对象来封装一系列的对象交互&#xff0c;中介者使各对象不需要显式地相互引用&#xff0c;从而使其耦合松散&#xff0c;而且可以独立地改变它们之间的交互。 实现&#xff1a;抽象中介者类&#xff0c;定义一个…

服装设计网上自学课程宁波网站搜索引擎优化

以下文字是钱学森在1990年给汪成为院士的信中对虚拟现实技术的科学意义和未来发展给与的思考和建议&#xff0c;他也提出可以将虚拟现实技术成为灵境技术。

IOS-和安卓-AR-游戏开发指南-全-

IOS 和安卓 AR 游戏开发指南(全)原文:zh.annas-archive.org/md5/eaf1b154611090aa6422cd5e3d6dc2fc 译者:飞龙 协议:CC BY-NC-SA 4.0前言 在本书中,我们将介绍增强现实及其如何使用强大而简单的工具实现。利用 V…

Winform/C# 输出到Release VS中Release模式下生成去掉生成pdb文件

前几天发布项目,有时候就发布那几个dll,但是一个dll同时还有一个pdb文件,而且pdb文件貌似还挺大。 pdb文件包含了编译后程序指向源代码的位置信息,用于调试的时候定位到源代码,主要是用来方便调试的. 在程序发布为r…

成都响应网站建设网站文章标题

一、实验内容与目的 实验要求&#xff1a; 利用CP226实验仪上的小键盘将程序输入主存储器EM&#xff0c;通过指令的执行实现微程序控制器的程序控制。 实验目的&#xff1a; 1.掌握模型机的操作码测试过程&#xff1b; 2.掌握模型机微程序控制器的基本结构以及程序控制的基本原…

表格上传网站建设网游小说

1. 引言 在任何编程语言中,错误处理都是一个至关重要的部分。在 Go 语言中,错误处理方式独具特色,它并没有采用异常处理机制(try-catch),而是通过显式的错误返回值来处理错误。这种方式让代码更加明确、易于维护,也使得错误处理更加透明。 在这篇博客中,我们将深入探…

做外贸好的网站有哪些百度识图在线

目录 0、基本信息1、研究动机2、创新点2.1、核心思想&#xff1a;2.2、思想推导&#xff1a; 3、准备3.1、符号3.2、互信息3.3、JS散度3.4、Deep InfoMax方法3.5、判别器&#xff1a;f-GAN估计散度 4、具体实现4.1、局部-全局互信息最大化4.2、理论动机 5、实验设置5.1、直推式…

校园二手网站开发与设计任务书行政单位单位网站建设

1.实现一个纵横字谜 2.支持14x14的网格 3.可以查看答案 4.猜测错误会提示答案信息 5.从txt读取词汇 6.每次游戏开始 随机生成纵横字谜 n’h

网站推他网站wordpress教程登陆

1.为什么要有缓冲区 缓冲区分成语言层面的缓冲区和操作系统层面的缓冲区 先说结论&#xff0c;语言的缓冲区可以减少系统调用的次数进而提高向文件写入和读取的效率。 2.举例子 向屏幕打印&#xff0c;无非就是向屏幕这个文件的缓冲区写入&#xff0c;然后在由操作系统刷新…

重庆工程建设信息网站4399谁做的网站

log函数是指数函数y bx 的反函数,用于求数字以某个数为底的对数。log函数的定义:设b>0,b≠1,对于任意实数x > 0,如果存在唯一的实数y,使得 b^y x,则称y为以b为底x的对数,记为:y log_b(x)这里b称为对数的底数。对数运算的底数通常取10和e。常见的对数运算有:1. 常用对数…

公司核名在哪个网站网站提交搜索引擎后出现问题

作者&#xff1a;Zarten知乎专栏&#xff1a;Python爬虫深入详解知乎ID&#xff1a; Zarten简介&#xff1a; 互联网一线工作者&#xff0c;尊重原创并欢迎评论留言指出不足之处&#xff0c;也希望多些关注和点赞是给作者最好的鼓励 &#xff01;介绍MongoDB是一种面向文档型的…

供应商协同平台:打造高效安全供应链的关键

供应商协同平台通过整合技术资源,解决了传统供应链中文件传输混乱、数据更新延迟、安全管控薄弱等问题。结合“Ftrans B2B企业间⽂件安全交换系统”的加密传输与权限管理功能,平台实现了设计图纸、订单数据等关键信息…

互斥锁和信号量机制

互斥锁 特性: 1.需要忙等,进程时间片用完才下处理机,违反让权等待 2.优点:等待奇迹不用切换进程上下文,多处理机系统中,若上锁的时间短,则等待的代价很低 3.常用于多处理机,一个核忙等,其他核照常工作,并快速…

NSIS为当前用户安装和为所有用户安装的选择

一、为当前用户和所有用户安装选择 确定 NSIS 脚本中应使用 SetShellVarContext all 还是 current,主要取决于你的软件安装目标和用户访问需求。可以通过以下几个核心问题来判断: 1. 软件是否需要被系统中所有用户访…

在 Unity 中运用 SoundTouch 插件控制音频倍速播放

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

网站的商桥怎么做国内产品设计公司前十名

内容管理模块 - 课程预览、提交审核 文章目录 内容管理模块 - 课程预览、提交审核一、课程预览1.1 需求分析1.2 freemarker 模板引擎1.2.1 Maven 坐标1.2.2 freemaker 相关配置信息1.2.3 添加模板 1.3 测试静态页面1.3.1 部署Nginx1.3.2 解决端口问题被占用问题1.3.3 配置host文…

网站建设综合实训报告公司建设网站需要什么条件

介绍: lag() 是一种常用的窗口函数&#xff0c;它用于获取某一行之前的行的值。它可以用来在结果集中的当前行之前访问指定列的值。 用法: lag() 函数的语法如下&#xff1a; lag(列名, 偏移量, 默认值) over (partition by 列名1, 列名2, ... order by 列名 [asc|desc], .…