You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

优化Excel数据导入速度:IronXL与Office Interop对比及优化咨询

Excel数据导入性能优化问题:Office Interop vs IronXL

我写了一个从.xlsx文件导入数据并生成大量XML文件的程序。一开始用IronXL实现,但发现发布时它不是免费选项,所以换成了Office Interop。现在两种实现都能正常工作,但IronXL速度快很多——处理两个工作表时,IronXL约8秒,Office Interop要40秒,后续要处理10-15个工作表,耗时差距会更大。想问下我的Office Interop代码能不能优化,还是说IronXL天生就更快,没优化空间?


Office Interop实现代码

主加载方法

static public void LoadExcelDataInterop()
{
    // 默认浮点数导入时用逗号作为小数分隔符,比如12,5而不是12.5
    // 代码里其他地方都要求用12.5的格式
    // 之前把武器数据输出到文件时是逗号格式,导致了错误
    System.Globalization.CultureInfo customCulture = (System.Globalization.CultureInfo)System.Threading.Thread.CurrentThread.CurrentCulture.Clone();
    customCulture.NumberFormat.NumberDecimalSeparator = ".";
    System.Threading.Thread.CurrentThread.CurrentCulture = customCulture;

    Excel.Application xlApp = new Excel.Application();
    Excel.Workbook wb = xlApp.Workbooks.Open(@"D:\Outward - r2modman\Mods\OutwardDe\profiles\Outward\BepInEx\plugins\Outward_Mod_Weapons_ValuesToImport.xlsx", 0, true, 5, "", "", true, Microsoft.Office.Interop.Excel.XlPlatform.xlWindows, "\t", false, false, 0, true, 1, 0);

    foreach (Excel.Worksheet ws in wb.Worksheets)
    {
        if (ws.Name == "Swords_1h" || ws.Name == "Swords_2h"
            //     || ws.Name == "Axes_1h"    || ws.Name == "Axes_2h"
            //     || ws.Name == "Maces_1h"   || ws.Name == "Maces_2h"
            //     || ws.Name == "Halberds"   || ws.Name == "Staves"
            //     || ws.Name == "Spears"     || ws.Name == "Gauntlets"
            //     || ws.Name == "Bows"       || ws.Name == "Shields"
            //     || ws.Name == "Chakrams"   || ws.Name == "Daggers"     || ws.Name == "Pistols"
            )
        {

            for (int x = 2; x > 0; x++)
            {
                var cell = ws.Cells[x, 2] as Excel.Range;
                string cell_name = (string)cell.Value;

                if (cell_name == null) { break; }
                else
                {
                    dict_Weapons.Add(cell_name, new SL_Weapon(wb, ws, cell));
                }
            }
        }
        Marshal.ReleaseComObject(ws);
    }

    wb.Close(false, null, null);
    xlApp.Quit();

    Marshal.ReleaseComObject(wb);
    Marshal.ReleaseComObject(xlApp);
}

SL_Weapon构造函数

public SL_Weapon(Excel.Workbook wb, Excel.Worksheet ws, Excel.Range cell)
{
    StatsHolder = new SL_WeaponStats
    {
        Damage_Bonus = new float[9],
        Damage_Resistance = new float[9],
    };
    var SH = ((SL_WeaponStats)StatsHolder);
    SH.BaseDamage = new List<SL_Damage>();
    Effects = new List<OE_Effect>();

    for (int i = 1; i <= ws.Columns.Count; i++)
    {
        var heading = ws.Cells[1, i] as Excel.Range;
        string headingName = (string)heading.Value;

        var workingCell = ws.Cells[cell.Row, heading.Column] as Excel.Range;
        var workingCell_nextCell = ws.Cells[cell.Row, heading.Column + 1] as Excel.Range;

        if (headingName == null) { break; }
        else if (workingCell.Value == null) { continue; }
        else
        {
            if (headingName == "Name") { Name = (string)workingCell.Value; }
            if (headingName == "ID") { Target_ItemID = New_ItemID = (int)workingCell.Value; }
            
            if (headingName == "DMG Physical") { SH.BaseDamage.Add(new SL_Damage { Damage = (float)workingCell.Value, Type = "Physical" }); }
            if (headingName == "DMG 2") { SH.BaseDamage.Add(new SL_Damage { Damage = (float)workingCell.Value, Type = (string)workingCell_nextCell.Value }); }
            if (headingName == "DMG 3") { SH.BaseDamage.Add(new SL_Damage { Damage = (float)workingCell.Value, Type = (string)workingCell_nextCell.Value }); }

            // 选项1:通过反射赋值StatsHolder字段——不行,会报错(无法将System.Single转换为System.Int32)
            /*
            FieldInfo[] fields = typeof(SL_WeaponStats).GetFields();
            foreach (var field in fields)
            {
                if (headingName == field.Name) { field.SetValue(StatsHolder, (float)workingCell.Value); }
            }
            */

            // 选项2:直接赋值StatsHolder字段
            if (headingName == "MaxDurability") { SH.MaxDurability = (int)workingCell.Value; }
            if (headingName == "RawWeight") { SH.RawWeight = (float)workingCell.Value; }
            if (headingName == "BaseValue") { SH.BaseValue = (int)workingCell.Value; }

            if (headingName == "StamCost") { SH.StamCost = (float)workingCell.Value; }
            if (headingName == "AttackSpeed") { SH.AttackSpeed = (float)workingCell.Value; }
            if (headingName == "Impact") { SH.Impact = (float)workingCell.Value; }
            
            if (workingCell_nextCell.Value != null) 
            {
                if (headingName == "Effect 1" || headingName == "Effect 2" || headingName == "Effect 3")
                { Effects.Add(new OE_Effect { StatusEffect = (string)workingCell.Value, Buildup = (int)workingCell_nextCell.Value }); }

            }
        }

        foreach (Excel.Worksheet worksheet in wb.Worksheets)
        {
            if (worksheet.Name == "Damage_BonusOrRes")
            {
                var item_dmgBonus = ((SL_EquipmentStats)StatsHolder).Damage_Bonus;
                var item_dmgResistance = ((SL_EquipmentStats)StatsHolder).Damage_Resistance;

                for (int x = 2; x < 10; x++ )
                {
                    Excel.Range workingCell2 = worksheet.Cells[x, 1] as Excel.Range;
                    string workingCell2Name = (string)workingCell2.Value;
                    if (workingCell2Name == null) { break; }
                    else if (workingCell2.Value.ToString() == cell.Value.ToString())
                    {
                        for (int y = 0; y < 6; y++)
                        {
                            if ((worksheet.Cells[x, y + 2] as Excel.Range).Value != null)
                            {
                                item_dmgBonus[y] = (float)(worksheet.Cells[x, y + 2] as Excel.Range).Value;
                            }
                            if ((worksheet.Cells[x, y + 8] as Excel.Range).Value != null)
                            {
                                item_dmgResistance[y] = (float)(worksheet.Cells[x, y + 8] as Excel.Range).Value;
                            }

                        }
                    }
                }
                
            }
        }
    }
}

IronXL实现代码

主加载方法

public static void LoadExcelDataIronXL()
{
    // 默认IronXL导入浮点数时用逗号作为小数分隔符,比如12,5而不是12.5
    // 代码里其他地方都要求用12.5的格式
    // 之前把武器数据输出到文件时是逗号格式,后续复制到其他程序时因为格式不对报错了
    System.Globalization.CultureInfo customCulture = (System.Globalization.CultureInfo)System.Threading.Thread.CurrentThread.CurrentCulture.Clone();
    customCulture.NumberFormat.NumberDecimalSeparator = ".";
    System.Threading.Thread.CurrentThread.CurrentCulture = customCulture;


    WorkBook wb = WorkBook.Load("D:/Outward - r2modman/Mods/OutwardDe/profiles/Outward/BepInEx/plugins/Outward_Mod_Weapons_ValuesToImport.xlsx");

    void LoadExcel_Weapons()
    {
        foreach (var ws in wb.WorkSheets)
        {
            if (ws.Name == "Swords_1h" || ws.Name == "Swords_2h"
            //     || ws.Name == "Axes_1h"    || ws.Name == "Axes_2h"
            //     || ws.Name == "Maces_1h"   || ws.Name == "Maces_2h"
            //     || ws.Name == "Halberds"   || ws.Name == "Staves"
            //     || ws.Name == "Spears"     || ws.Name == "Gauntlets"
            //     || ws.Name == "Bows"       || ws.Name == "Shields"
            //     || ws.Name == "Chakrams"   || ws.Name == "Daggers"     || ws.Name == "Pistols"
               )
            {
                foreach (var item in ws.Columns[1])
                {
                    if (item.IsEmpty) { break; }
                    else if (item.RowIndex == 0) { continue; }
                    else
                    {
                        dict_Weapons.Add(item.Value.ToString(), new SL_Weapon(wb, ws.Name, item));
                    }
                }
            }
        }
    }
    LoadExcel_Weapons();
}

(注:原代码存在笔误,已修正多余的item.Value.ToString());语句)

SL_Weapon构造函数

public SL_Weapon(WorkBook wb, string worksheetName, Cell cell)
{
    WorkSheet ws = wb.GetWorkSheet(worksheetName);
    RangeRow row = ws.Rows[cell.RowIndex];
    StatsHolder = new SL_WeaponStats
    {
        Damage_Bonus = new float[9],
        Damage_Resistance = new float[9],
    };
    var SH = ((SL_WeaponStats)StatsHolder);
    SH.BaseDamage = new List<SL_Damage>();
    Effects = new List<OE_Effect>();

    foreach (var heading in ws.Rows[0])
    {
        var headingColumn = heading.ColumnIndex;
        var headingName = heading.ToString();

        if (headingName == "Name") { Name = row.Columns[headingColumn].ToString(); }
        if (headingName == "ID") { Target_ItemID = New_ItemID = row.Columns[headingColumn].IntValue; }

        if (headingName == "DMG Physical") { SH.BaseDamage.Add(new SL_Damage { Damage = row.Columns[headingColumn].FloatValue, Type = "Physical" }); }
        if (headingName == "DMG 2") { SH.BaseDamage.Add(new SL_Damage { Damage = row.Columns[headingColumn].FloatValue, Type = row.Columns[headingColumn + 1].ToString() }); }
        if (headingName == "DMG 3") { SH.BaseDamage.Add(new SL_Damage { Damage = row.Columns[headingColumn].FloatValue, Type = row.Columns[headingColumn + 1].ToString() }); }
        
        if (headingName == "MaxDurability")     { SH.MaxDurability = row.Columns[headingColumn].IntValue; }
        if (headingName == "RawWeight")         { SH.RawWeight = row.Columns[headingColumn].FloatValue; }
        if (headingName == "BaseValue")         { SH.BaseValue = row.Columns[headingColumn].IntValue; }

        if (headingName == "StamCost")          { SH.StamCost = row.Columns[headingColumn].FloatValue; }
        if (headingName == "AttackSpeed")       { SH.AttackSpeed = row.Columns[headingColumn].FloatValue; }
        if (headingName == "Impact")            { SH.Impact = row.Columns[headingColumn].FloatValue; }
        

        if (headingName == "Effect 1") { Effects.Add(new OE_Effect { StatusEffect = row.Columns[headingColumn].ToString(), Buildup = row.Columns[headingColumn + 1].IntValue }); }
        if (headingName == "Effect 2") { Effects.Add(new OE_Effect { StatusEffect = row.Columns[headingColumn].ToString(), Buildup = row.Columns[headingColumn + 1].IntValue }); }
        if (headingName == "Effect 3") { Effects.Add(new OE_Effect { StatusEffect = row.Columns[headingColumn].ToString(), Buildup = row.Columns[headingColumn + 1].IntValue }); }

    }

    var ws_DmgBonusOrRes = wb.GetWorkSheet("Damage_BonusOrRes");
    var item_dmgBonus = ((SL_EquipmentStats)StatsHolder).Damage_Bonus;
    var item_dmgResistance = ((SL_EquipmentStats)StatsHolder).Damage_Resistance;

    foreach (var cell1 in ws_DmgBonusOrRes.Columns[0])
    {
        if (cell1.IsEmpty) { break; }
        else if (cell1.RowIndex == 0) { continue; }
        else if (cell1.Value.ToString() == cell.ToString())
        {
            for (int i = 0; i < 6; i++)
            {
                item_dmgBonus[i] = ws_DmgBonusOrRes.Rows[cell1.RowIndex].Columns[i + 1].FloatValue;
                item_dmgResistance[i] = ws_DmgBonusOrRes.Rows[cell1.RowIndex].Columns[i + 7].FloatValue;
            }
        }
    }
}

优化建议

1. 批量读取单元格数据,避免逐个访问

Office Interop每次单独访问Range对象都会和Excel进程交互,开销极大。可以一次性把整个工作表的已用区域数据读取到二维数组里,再在内存中处理:

// 在主方法中读取整个工作表的已用区域数据
var usedRange = ws.UsedRange;
object[,] data = usedRange.Value as object[,];
// 之后通过数组索引访问数据,比如data[rowIndex, columnIndex]

这是Office Interop性能提升的核心优化点,能大幅减少跨进程调用的次数。

2. 预加载Damage_BonusOrRes工作表数据

你的SL_Weapon构造函数中,每次循环列都会遍历所有工作表查找Damage_BonusOrRes,还会逐行匹配武器名称。可以提前把这个工作表的数据读取到字典中,比如Dictionary<string, (float[] bonus, float[] resistance)>,后续直接通过武器名称查询,无需反复遍历工作表。

3. 关闭Excel界面交互

Office Interop默认会启动Excel进程并显示界面,关闭界面相关设置能减少资源消耗:

xlApp.Visible = false;
xlApp.ScreenUpdating = false;
xlApp.DisplayAlerts = false;

批量处理时关闭这些设置能显著提速,退出前可根据需要恢复。

4. 优化COM对象释放逻辑

频繁调用Marshal.ReleaseComObject会增加额外开销,且容易引发内存泄漏问题。可以在所有COM操作完成后统一释放对象,或使用包装类简化释放流程,避免逐个释放工作表等对象。

关于IronXL的性能优势

IronXL是直接解析Excel文件格式(无需依赖Excel进程),属于进程内操作;而Office Interop通过COM调用外部Excel进程,属于跨进程操作,本身就有更高的通信开销。因此IronXL天生在性能上有优势,但通过上述优化,Office Interop的性能能大幅接近IronXL,满足你的业务需求。


内容的提问来源于stack exchange,提问作者Marcin Pagórek

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 18:51:03