一万条数据要导出EXCEL太慢了10分钟左右

本文介绍了一种高效处理大量数据导出至Excel的方法,包括使用NPOI库进行数据流操作,自动调整列宽,以及如何从DataReader或DataTable转换数据至Excel流,最后保存至文件或直接输出至客户端。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using ICSharpCode;
using NPOI.XSSF.UserModel;
using NPOI.HSSF.UserModel;
using NPOI.SS.UserModel;
using System.Data;
using System.Reflection;
using System.Text.RegularExpressions;

    public class WordHelp
    {

        #region Excel列类型获取列的值
        /// <summary>
        /// 根据Excel列类型获取列的值
        /// </summary>
        /// <param name="cell">Excel列</param>
        /// <returns></returns>
        private static string GetCellValue(ICell cell)
        {
            if (cell == null)
                return string.Empty;
            switch (cell.CellType)
            {
                case CellType.Blank:
                    return string.Empty;
                case CellType.Boolean:
                    return cell.BooleanCellValue.ToString();
                case CellType.Error:
                    return cell.ErrorCellValue.ToString();
                case CellType.Numeric:
                case CellType.Unknown:
                default:
                    return cell.ToString();//This is a trick to get the correct value of the cell. NumericCellValue will return a numeric value no matter the cell value is a date or a number
                case CellType.String:
                    return cell.StringCellValue;
                case CellType.Formula:
                    try
                    {
                        XSSFFormulaEvaluator e = new XSSFFormulaEvaluator(cell.Sheet.Workbook);
                        e.EvaluateInCell(cell);
                        return cell.ToString();
                    }
                    catch
                    {
                        return cell.NumericCellValue.ToString();
                    }
            }
        }
        #endregion

        //数据量过大会造成响应时间过长
        /// <summary>
        /// 自动设置Excel列宽
        /// </summary>
        /// <param name="sheet">Excel表</param>
        //private static void AutoSizeColumns(ISheet sheet)
        //{
        //    if (sheet.PhysicalNumberOfRows > 0)
        //    {
        //        IRow headerRow = sheet.GetRow(0);

        //        for (int i = 0, l = headerRow.LastCellNum; i < l; i++)
        //        { 
        //            sheet.AutoSizeColumn(i);
        //        }
        //    }
        //}

        /// <summary>
        /// 保存Excel文档流到文件
        /// </summary>
        /// <param name="ms">Excel文档流</param>
        /// <param name="fileName">文件名</param>
        private static void SaveToFile(MemoryStream ms, string fileName)
        {
            using (FileStream fs = new FileStream(fileName, FileMode.Create, FileAccess.Write))
            {
                byte[] data = ms.ToArray();

                fs.Write(data, 0, data.Length);
                fs.Flush();

                data = null;
            }
        }

        ///// <summary>
        ///// 输出文件到浏览器
        ///// </summary>
        ///// <param name="ms">Excel文档流</param>
        ///// <param name="context">HTTP上下文</param>
        ///// <param name="fileName">文件名</param>
        //private static void RenderToBrowser(MemoryStream ms, HttpContext context, string fileName)
        //{
        //    if (context.Request.Browser.Browser == "IE")
        //        fileName = HttpUtility.UrlEncode(fileName);
        //    context.Response.AddHeader("Content-Disposition", "attachment;fileName=" + fileName);
        //    context.Response.BinaryWrite(ms.ToArray());
        //}

        /// <summary>
        /// DataReader转换成Excel文档流
        /// </summary>
        /// <param name="reader"></param>
        /// <returns></returns>
        public static MemoryStream RenderToExcel(IDataReader reader)
        {
            MemoryStream ms = new MemoryStream();

            using (reader)
            {
                IWorkbook workbook = new XSSFWorkbook();

                ISheet sheet = workbook.CreateSheet();

                IRow headerRow = sheet.CreateRow(0);
                int cellCount = reader.FieldCount;

                // handling header.
                for (int i = 0; i < cellCount; i++)
                {
                    headerRow.CreateCell(i).SetCellValue(reader.GetName(i));
                }

                // handling value.
                int rowIndex = 1;
                while (reader.Read())
                {
                    IRow dataRow = sheet.CreateRow(rowIndex);

                    for (int i = 0; i < cellCount; i++)
                    {
                        dataRow.CreateCell(i).SetCellValue(reader[i].ToString());
                    }

                    rowIndex++;
                }

                //AutoSizeColumns(sheet);

                workbook.Write(ms);
                ms.Flush();
                ms.Position = 0;
            }

            return ms;
        }

        /// <summary>
        /// DataReader转换成Excel文档流,并保存到文件
        /// </summary>
        /// <param name="reader"></param>
        /// <param name="fileName">保存的路径</param>
        public static void RenderToExcel(IDataReader reader, string fileName)
        {
            using (MemoryStream ms = RenderToExcel(reader))
            {
                SaveToFile(ms, fileName);
            }
        }

        ///// <summary>
        ///// DataReader转换成Excel文档流,并输出到客户端
        ///// </summary>
        ///// <param name="reader"></param>
        ///// <param name="context">HTTP上下文</param>
        ///// <param name="fileName">输出的文件名</param>
        //public static void RenderToExcel(IDataReader reader, HttpContext context, string fileName)
        //{
        //    using (MemoryStream ms = RenderToExcel(reader))
        //    {
        //        RenderToBrowser(ms, context, fileName);
        //    }
        //}

        /// <summary>
        /// DataTable转换成Excel文档流
        /// </summary>
        /// <param name="table"></param>
        /// <returns></returns>
        public static MemoryStream RenderToExcel(DataTable table)
        {
            MemoryStream ms = new MemoryStream();

            using (table)
            {
                IWorkbook workbook = new XSSFWorkbook();

                ISheet sheet = workbook.CreateSheet();

                IRow headerRow = sheet.CreateRow(0);

                // handling header.
                foreach (DataColumn column in table.Columns)
                    headerRow.CreateCell(column.Ordinal).SetCellValue(column.Caption);//If Caption not set, returns the ColumnName value
                // handling value.
                int rowIndex = 1;

                foreach (DataRow row in table.Rows)
                {
                    IRow dataRow = sheet.CreateRow(rowIndex);

                    foreach (DataColumn column in table.Columns)
                    {
                        dataRow.CreateCell(column.Ordinal).SetCellValue(row[column].ToString());
                    }

                    rowIndex++;
                }
                //AutoSizeColumns(sheet);

                workbook.Write(ms);
                ms.Flush();
                //ms.Position = 0;
            }

            return ms;
        }

        /// <summary>
        /// DatasSet转换成Excel文档流
        /// </summary>
        /// <param name="sourceDs">DataSet</param>
        /// <param name="sheetName">工作表命,按逗号分隔</param>
        /// <returns></returns>
        private static MemoryStream RenderToExcel(DataSet sourceDs, string sheetName)
        {
            IWorkbook workbook = new XSSFWorkbook();
            MemoryStream ms = new MemoryStream();
            string[] sheetNames = sheetName.Split(',');
            for (int i = 0; i < sheetNames.Length; i++)
            {
                ISheet sheet = workbook.CreateSheet(sheetNames[i]);
                IRow headerRow = sheet.CreateRow(0);
                // handling header. 
                foreach (DataColumn column in sourceDs.Tables[i].Columns)
                    headerRow.CreateCell(column.Ordinal).SetCellValue(column.ColumnName);
                // handling value. 
                int rowIndex = 1;
                foreach (DataRow row in sourceDs.Tables[i].Rows)
                {
                    IRow dataRow = sheet.CreateRow(rowIndex);

                    foreach (DataColumn column in sourceDs.Tables[i].Columns)
                    {
                        dataRow.CreateCell(column.Ordinal).SetCellValue(row[column].ToString());
                    }
                    rowIndex++;
                }
            }
            workbook.Write(ms);
            ms.Flush();
            //ms.Position = 0;
            //workbook = null;
            return ms;
        }

        /// <summary>
        /// DataTable转换成Excel文档流,并保存到文件
        /// </summary>
        /// <param name="table"></param>
        /// <param name="fileName">保存的路径</param>
        public static void RenderToExcel(DataTable table, string fileName)
        {
            using (MemoryStream ms = RenderToExcel(table))
            {
                SaveToFile(ms, fileName);
            }
        }

        ///// <summary>
        ///// DataTable转换成Excel文档流,并输出到客户端
        ///// </summary>
        ///// <param name="table"></param>
        ///// <param name="response"></param>
        ///// <param name="fileName">输出的
论坛发帖机文件名</param>
        //public static void RenderToExcel(DataTable table, HttpContext context, string fileName)
        //{
        //    using (MemoryStream ms = RenderToExcel(table))
        //    {
        //        RenderToBrowser(ms, context, fileName);
        //    }
        //}

        #region Excel验证是否有数据
        /// <summary>
        /// Excel文档流是否有数据
        /// </summary>
        /// <param name="excelFileStream">Excel文档流</param>
        /// <returns></returns>
        public static bool HasData(Stream excelFileStream)
        {
            return HasData(excelFileStream, 0);
        }

        /// <summary>
        /// Excel文档流是否有数据
        /// </summary>
        /// <param name="

 这个很正常,微软提供的东西就是那么的慢。因为不开源,所以你还不能优化。

 如果你要快点,可以从COM+ 底层API入手,直接调用,或许能增加速率。不过COM+ 貌似已经淘汰许久。

开一个线程,让他自己慢慢生成去。
生成完了,就通知一下就好了。

你1万条数据要放到excel啊。。。慢也是正常。
我不清楚能不能 使用 并行方式Parallel  能不能同时操作一个excel,把数据整进去。。。感觉不太行

如果是单纯的想快速导出,而且,数据中没有特殊符号(主要是TAB和回车),可以试试:用string字符串保存导出的内容,两个单元格之间用TAB分隔,两行之间用回车分隔,全部导出完成后,新建一个空EXCEL,再将结果粘贴进去。

可以试试第三方插件NOPI 效率还可以

我的30万条数据导出Excel不到两分钟

<think>好的,我现在需要帮助用户优化Apache POI的SXSSFWorkbook导出十多万条数据Excel的性能。首先,我得回想一下SXSSFWorkbook的工作原理。SXSSFWorkbook是POI的流式API,专门用于处理大数据量的导出,它通过滑动窗口机制在内存中只保留一定数量的行,其他的行会被写入磁盘临时文件,这样可以减少内存占用[^1]。 用户提到导出十多万条数据,可能遇到内存不足或速度的问题。根据之前的引用内容,分批次读取数据、流式写入、调整缓存大小和关闭连接是关键。接下来我需要具体分析每个优化点。 首先,分批次读取数据。如果用户的数据源是数据库,使用分页查询(LIMIT和OFFSET)来分批获取数据,而不是一次性加载所有数据到内存。这样可以避免内存峰值,同时减轻数据库压力。例如,每批读取1000条,处理完再读下一批。 然后是流式写入。确保在创建SXSSFWorkbook时设置适当的窗口大小,比如new SXSSFWorkbook(1000),这样内存中最多保留1000行,超过的部分会被写入临时文件。但要注意,窗口大小不能太小,否则频繁的磁盘IO会影响性能。需要根据数据行的大小和可用内存找到一个平衡点。 接下来是缓存设置。POI的默认缓存可能不适合大数据量,调整SXSSFWorkbook的压缩临时文件的缓存大小,比如使用setCompressTempFiles(true)来启用压缩,减少磁盘空间占用。同时,可以通过系统属性设置临时文件的位置到SSD等高速存储设备,加快IO速度。 样式优化也是一个重点。避免为每个单元格单独创建样式,因为样式对象会占用大量内存。应该提前创建所需的样式,并在多个单元格中复用。例如,定义标题样式、数据样式等,然后在循环中重复使用这些样式对象。 关闭不必要的资源。处理完每一批数据后,及时调用flushRows()方法将数据写入磁盘,并清理不再需要的行。最后,在导出完成后,调用dispose()方法删除临时文件,释放资源。同时,确保关闭Workbook和输出流,避免资源泄漏。 数据生成的优化方面,可以考虑禁用公式计算和链接检查,使用SXSSFSheet的setRandomAccessWindowSize()来控制访问窗口的大小,减少内存中的缓存数据量。此外,如果不需要某些特性,比如自动调整列宽,可以在导出完成后再统一调整,避免每次写入都计算列宽。 还有一些其他注意事项,比如避免在循环中创建大量临时对象,尽量重用对象;使用字符串缓存池(如POI的SharedStringsTable)来减少重复字符串的内存占用;如果数据中有大量重复内容,可以提前处理成共享字符串,节省空间。 最后,参考引用[1]中的建议,分页导出是关键。结合分批次读取和流式写入,确保每个批次的数据处理完毕后及时释放内存。同时,监控内存使用情况和导出时间,根据实际情况调整批次大小和窗口大小,找到最优配置。 总结下来,优化步骤包括分批次处理、调整窗口和缓存、样式复用、资源管理和数据生成优化。需要逐一检查用户的代码,看是否有可以改进的地方,比如是否有不必要的样式创建,是否正确关闭资源,是否使用了分页查询等。此外,建议用户进行性能测试,逐步调整参数以达到最佳效果。</think>### 优化Apache POI SXSSFWorkbook导出大量数据的性能措施 #### 1. **分批次处理数据** - **分页读取数据源**:若数据来自数据库,使用分页查询(如`LIMIT`和`OFFSET`)逐批加载数据,避免一次性加载所有数据到内存。例如: ```java int pageSize = 1000; for (int page = 0; ; page++) { List<Data> batch = queryDataFromDatabase(page * pageSize, pageSize); if (batch.isEmpty()) break; writeBatchToExcel(batch); } ``` - **流式写入行**:每处理完一批数据后立即写入Excel,减少内存占用。 #### 2. **调整SXSSFWorkbook参数** - **设置合理的窗口大小**:通过构造函数`SXSSFWorkbook(windowSize)`控制内存中保留的行数。例如: ```java SXSSFWorkbook workbook = new SXSSFWorkbook(1000); // 内存中最多缓存1000行 ``` 窗口大小需根据数据行复杂度调整:行越复杂(如样式多),窗口应越小。 #### 3. **优化样式和格式** - **复用样式对象**:避免在循环中重复创建`CellStyle`。提前定义样式并复用: ```java CellStyle headerStyle = workbook.createCellStyle(); headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); // 后续所有标题单元格使用headerStyle ``` - **禁用自动调整列宽**:在写入数据时关闭`autoSizeColumn`,导出完成后再统一调整: ```java sheet.trackAllColumnsForAutoSizing(); // 数据写入完成后 for (int i = 0; i < columns; i++) { sheet.autoSizeColumn(i); } ``` #### 4. **管理临时文件和资源** - **启用临时文件压缩**:减少磁盘占用: ```java workbook.setCompressTempFiles(true); ``` - **手动清理临时文件**:导出完成后调用`dispose()`: ```java workbook.dispose(); // 删除临时文件 ``` #### 5. **减少内存开销** - **禁用公式计算**:若不需要公式,关闭相关功能: ```java workbook.setForceFormulaRecalculation(false); ``` - **使用字符串缓存**:对于重复的字符串值,利用`SharedStringsTable`优化存储: ```java // 默认启用,需确保数据中有重复文本 ``` #### 6. **代码示例 ```java try (SXSSFWorkbook workbook = new SXSSFWorkbook(1000)) { workbook.setCompressTempFiles(true); Sheet sheet = workbook.createSheet("Data"); // 预定义样式 CellStyle dataStyle = workbook.createCellStyle(); dataStyle.setDataFormat(workbook.createDataFormat().getFormat("@")); // 文本格式 int pageSize = 2000; for (int page = 0; ; page++) { List<Data> batch = fetchDataBatch(page, pageSize); if (batch.isEmpty()) break; for (Data item : batch) { Row row = sheet.createRow(currentRow++); Cell cell = row.createCell(0); cell.setCellValue(item.getId()); cell.setCellStyle(dataStyle); // 复用样式 // ...其他列 } // 每处理完一批次,手动刷新 if (currentRow % 1000 == 0) { ((SXSSFSheet) sheet).flushRows(1000); // 强制写入磁盘 } } // 最终调整列宽 for (int i = 0; i < columns; i++) { sheet.autoSizeColumn(i); } } ``` #### 7. **其他注意事项** - **输出流使用缓冲**:写入文件时使用`BufferedOutputStream`提升IO效率: ```java try (FileOutputStream fos = new FileOutputStream("output.xlsx"); BufferedOutputStream bos = new BufferedOutputStream(fos)) { workbook.write(bos); } ``` - **JVM参数调优**:增加堆内存(如`-Xmx4G`)并设置临时目录为高速存储: ```bash -Djava.io.tmpdir=/path/to/ssd/tmp ``` --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值