阿里easyexcel解析百万级大数据量的Excel表格,看这一篇文章就够了

本文涉及的产品
云原生大数据计算服务MaxCompute,500CU*H 100GB 3个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 阿里easyexcel解析百万级大数据量的Excel表格,看这一篇文章就够了

​1、应用场景

1.1、实际工作中可能会遇到百万条数据量的Excel表格上传到后台进行解析。

那么传统的POI,它只适用于数据量较小的Excel表格解析,当数据量到一定级别时,后台程序就会报出类似内存溢出的错误

6c12d57dd26ab698def523f22d24f284.png


1.2、POI提供了两种读取Excel的模式,分别是

用户模式:也就是poi下的usermodel有关包,它对用户友好,有统一的接口在ss包下,但是它是把整个文件读取到内存中的,对于大量数据很容易内存溢出,所以只能用来处理相对较小量的数据;
事件模式:在poi下的eventusermodel包下,相对来说实现比较复杂,但是它处理速度快,占用内存少,可以用来处理海量的Excel数据。

2、解决方案

2.1、首先业务上将单个大数据量的Excel表拆分成多个Exce文件,进行批量上传。

2.2、后台采用线程池异步处理解析数据以及存储数据

3、具体实现过程

3.1、pom.xml文件引入相关的依赖包

<!-- POI相关的包 -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>ooxml-schemas</artifactId>
            <version>1.1</version>
        </dependency>
        <dependency>
            <groupId>fr.opensagres.xdocreport</groupId>
            <artifactId>org.apache.poi.xwpf.converter.core</artifactId>
            <version>1.0.6</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>3.17</version>
        </dependency>
        
        <!-- easyexcel依赖包 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>2.2.7</version>
        </dependency>

注:此处我将POI相关的包以及EasyExcel的包都引进来了。

3.2、后台Java处理逻辑

logger.info("==============开始导入企业表数据====================userid={},oldOperateId={},type={}",zkjcUser.getId(),oldOperateId,type);
        MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
        MultipartFile file = multipartRequest.getFile("file");
        if(file!=null&&file.getSize()<=0){
            log.error("导入失败,"+file.getOriginalFilename()+",文件无内容!");
        }
       //保存上传文件到本地(异常时查看)
        File tempFile=null;
        try {
            //文件夹不存在则创建
            String os = System.getProperty("os.name");
            String sbLj="0".equals(type)?operateId:oldOperateId;
            String tempath="/temp/resources/upload/";
            if(os.toLowerCase().startsWith("win")){
                tempath="D:\\temp\\resources\\upload\\";
            }
            File fdir = new File(tempath);
            if (!fdir.exists()) { fdir.mkdirs(); }
            tempFile = new File(fdir.getPath()+File.separator+ originalFilename);
            file.transferTo(tempFile);
        } catch (IllegalStateException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        } catch (IOException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }
        
        String fileName = file.getOriginalFilename();
        fileName = fileName.substring(0, fileName.indexOf("."));
try {
            long t1 = new Date().getTime();
            logger.info("=======================开始解析上传文件===========================");
            ExcelParser parse = null;
            try {
//                InputStream inputStream = file.getInputStream();
                InputStream inputStream = new FileInputStream(tempFile);
                parse = new ExcelParser().parse(inputStream);
            } catch (InvalidFormatException | IOException | ParseException e) {
                logger.error("导入失败,解析excel异常:{}",e);
            }
            List<String[]> datas = parse.getDatas();
            if(null==datas ||(null!=datas&&datas.size()<=0)){
                logger.error("表格数据内容为空!请检查数据模板是否正确");
            }
            datas.remove(0);//移除表的字段标题行
            long t2 = new Date().getTime();
            logger.info("=======================POI解析出("+datas.size()+")条数据,POI解析上传文件耗时("+(t2-t1)+")===========================");
            List<List<String[]>> partitionList = ListUtils.partition(datas, 2000);//一个线程处理2千条数据
            // 创建一个线程池
            ExecutorService exec = Executors.newFixedThreadPool(10);
            // 定义一个任务集合
            List<Callable<List<ZkjcCompanyError>>> tasks = new ArrayList<>();
            Callable<List<ZkjcCompanyError>> task = null;
            for (List<String[]> list : partitionList) {
                task = new Callable<List<ZkjcCompanyError>>() {
                    @Override
                    public List<ZkjcCompanyError> call() throws Exception {
                       return zkjcCompanyService.saveBatch2(operateId,oldOperateId,list,type);
                    }
                };
                // 这里提交的任务容器列表和返回的Future列表存在顺序对应的关系
                tasks.add(task);
            }
            //执行任务
            List<ZkjcCompanyError> failList = new ArrayList<>();
            try{
                List<Future<List<ZkjcCompanyError>>> results = exec.invokeAll(tasks);
                for (Future<List<ZkjcCompanyError>> future : results) {
                    failList.addAll(future.get());
                }
            //任务执行结束,如果有异常数据,此处会返回封装到failList中,这里可以根据自己的业务做一些处理
            }catch (Exception e){
                logger.error("线程池执行任务异常:{}",e);
            }finally {
                // 关闭线程池
                exec.shutdown();
            }
            long t3 = new Date().getTime();
            logger.info("====================数据入库总计耗时("+(t3-t2)+")==============================");
        } catch (Exception e) {
            logger.error("导入表数据操作失败,发现异常:", e);
        }

3.3、上面核心逻辑处理部分用到的工具类

ExcelParser.java:核心解析工具类
package com.xxx.support.excel;
 
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
 
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xssf.eventusermodel.ReadOnlySharedStringsTable;
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler;
import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler.SheetContentsHandler;
import org.apache.poi.xssf.model.StylesTable;
import org.apache.poi.xssf.usermodel.XSSFComment;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;
 
 
public class ExcelParser {
     private static final Logger logger = LoggerFactory.getLogger(ExcelParser.class);
        /**
         * 表格默认处理器
         */
        private ISheetContentHandler contentHandler = new DefaultSheetHandler();
        /**
         * 读取数据
         */
        private List<String[]> datas = new ArrayList<String[]>();
 
        /**
         * 转换表格,默认为转换第一个表格
         * @param stream
         * @return
         * @throws InvalidFormatException
         * @throws IOException
         * @throws ParseException
         */
        public ExcelParser parse(InputStream stream)
                throws InvalidFormatException, IOException, ParseException {
            return parse(stream, 1);
        }
 
 
        /**
         * 
         * @param stream
         * @param sheetId:为要遍历的sheet索引,从1开始
         * @return
         * @throws InvalidFormatException
         * @throws IOException
         * @throws ParseException
         */
        public synchronized ExcelParser parse(InputStream stream, int sheetId)
                throws InvalidFormatException, IOException, ParseException {
            // 每次转换前都清空数据
            datas.clear();
            // 打开表格文件输入流
            OPCPackage pkg = OPCPackage.open(stream);
            try {
                // 创建表阅读器
                XSSFReader reader;
                try {
                    reader = new XSSFReader(pkg);
                } catch (OpenXML4JException e) {
                    logger.error("读取表格出错");
                    throw new ParseException(e.fillInStackTrace());
                }
 
                // 转换指定单元表
                InputStream shellStream = reader.getSheet("rId" + sheetId);
                try {
                    InputSource sheetSource = new InputSource(shellStream);
                    StylesTable styles = reader.getStylesTable();
                    ReadOnlySharedStringsTable strings = new ReadOnlySharedStringsTable(pkg);
                    getContentHandler().init(datas);// 设置读取出的数据
                    // 获取转换器
                    XMLReader parser = getSheetParser(styles, strings);
                    parser.parse(sheetSource);
                } catch (SAXException e) {
                    logger.error("读取表格出错");
                    throw new ParseException(e.fillInStackTrace());
                } finally {
                    shellStream.close();
                }
            } finally {
                pkg.close();
 
            }
            return this;
 
        }
 
        /**
         * 获取表格读取数据,获取数据前,需要先转换数据<br>
         * 此方法不会获取第一行数据
         * 
         * @return 表格读取数据
         */
        public List<String[]> getDatas() {
            return getDatas(true);
 
        }
 
        /**
         * 获取表格读取数据,获取数据前,需要先转换数据
         * 
         * @param dropFirstRow
         *            删除第一行表头记录
         * @return 表格读取数据
         */
        public List<String[]> getDatas(boolean dropFirstRow) {
            if (dropFirstRow && datas.size() > 0) {
                datas.remove(0);// 删除表头title
            }
            return datas;
 
        }
 
        /**
         * 获取读取表格的转换器
         * 
         * @return 读取表格的转换器
         * @throws SAXException
         *             SAX错误
         */
        protected XMLReader getSheetParser(StylesTable styles, ReadOnlySharedStringsTable strings) throws SAXException {
            XMLReader parser = XMLReaderFactory.createXMLReader();
            parser.setContentHandler(new XSSFSheetXMLHandler(styles, strings, getContentHandler(), false));
            return parser;
        }
 
        public ISheetContentHandler getContentHandler() {
            return contentHandler;
        }
 
        public void setContentHandler(ISheetContentHandler contentHandler) {
            this.contentHandler = contentHandler;
        }
 
        /**
         * 表格转换错误
         */
        public class ParseException extends Exception {
            private static final long serialVersionUID = -2451526411018517607L;
 
            public ParseException(Throwable t) {
                super("表格转换错误", t);
            }
 
        }
 
        public interface ISheetContentHandler extends SheetContentsHandler {
 
            /**
             * 设置转换后的数据集,用于存放转换结果
             * 
             * @param datas
             *            转换结果
             */
            void init(List<String[]> datas);
        }
 
        /**
         * 默认表格解析handder
         */
        class DefaultSheetHandler implements ISheetContentHandler {
            /**
             * 读取数据
             */
            private List<String[]> datas;
            private int columsLength;
            // 读取行信息
            private String[] readRow;
            private ArrayList<String> fristRow = new ArrayList<String>();
 
            @Override
            public void init(List<String[]> datas) {
                this.datas = datas;
//              this.columsLength = columsLength;
            }
 
            @Override
            public void startRow(int rowNum) {
                if (rowNum != 0) {
                    readRow = new String[columsLength];
                }
            }
 
            @Override
            public void endRow(int rowNum) {
            //将Excel第一行表头的列数当做数组的长度,要保证后续的行的列数不能超过这个长度,这是个约定。
                if (rowNum == 0) {
                    columsLength = fristRow.size();
                    readRow = fristRow.toArray(new String[fristRow.size()]);
                }else {
                    readRow = fristRow.toArray(new String[columsLength]);
                }
                datas.add(readRow.clone());
                readRow = null;
                fristRow.clear();
            }
 
            @Override
            public void cell(String cellReference, String formattedValue, XSSFComment comment) {
                int index = getCellIndex(cellReference);//转换A1,B1,C1等表格位置为真实索引位置
                try {
                    fristRow.set(index, formattedValue);
                } catch (IndexOutOfBoundsException e) {
                    int size = fristRow.size();
                    for (int i = index - size+1;i>0;i--){
                        fristRow.add(null);
                    }
                    fristRow.set(index,formattedValue);
                }
            }
 
            @Override
            public void headerFooter(String text, boolean isHeader, String tagName) {
            }
 
            /**
             * 转换表格引用为列编号
             * 
             * @param cellReference
             *            列引用
             * @return 表格列位置,从0开始算
             */
            public int getCellIndex(String cellReference) {
                String ref = cellReference.replaceAll("\\d+", "");
                int num = 0;
                int result = 0;
                for (int i = 0; i < ref.length(); i++) {
                    char ch = cellReference.charAt(ref.length() - i - 1);
                    num = (int) (ch - 'A' + 1);
                    num *= Math.pow(26, i);
                    result += num;
                }
                return result - 1;
            }
        }
        public static void main(String[] args) {
            ExcelParser parse = null;
            File file=new File("E:\\test\\logs\\aa.xlsx");
            try {
                InputStream inputStream = new FileInputStream(file);
                parse = new ExcelParser().parse(inputStream);
            } catch (InvalidFormatException | IOException | ParseException e) {
                logger.error("上传数据POI解析异常:{}",e);
            }
            List<String[]> datas = parse.getDatas();
            System.out.println(datas.size());
            for (String[] strings : datas) {
                System.out.println(strings);
            }
        }
        
    }

ListUtils.java 分割集合List的工具类可以在我的另一篇博文中查看

https://blog.csdn.net/weixin_36754290/article/details/123642486

如有错误或者需要指正的地儿可以在评论区留言或者私信小编,小编定会第一时间给予答复。

相关文章
|
4月前
|
机器学习/深度学习 供应链 大数据
【2023Mathorcup大数据】B题 电商零售商家需求预测及库存优化问题 python代码解析
本文提供了2023年MathorCup大数据竞赛B题的电商零售商家需求预测及库存优化问题的Python代码解析,涉及数据预处理、特征工程、时间序列预测、聚类分析以及模型预测性能评价等步骤。
202 0
|
2月前
|
机器学习/深度学习 搜索推荐 大数据
深度解析:如何通过精妙的特征工程与创新模型结构大幅提升推荐系统中的召回率,带你一步步攻克大数据检索难题
【10月更文挑战第2天】在处理大规模数据集的推荐系统项目时,提高检索模型的召回率成为关键挑战。本文分享了通过改进特征工程(如加入用户活跃时段和物品相似度)和优化模型结构(引入注意力机制)来提升召回率的具体策略与实现代码。严格的A/B测试验证了新模型的有效性,为改善用户体验奠定了基础。这次实践加深了对特征工程与模型优化的理解,并为未来的技术探索提供了方向。
114 2
深度解析:如何通过精妙的特征工程与创新模型结构大幅提升推荐系统中的召回率,带你一步步攻克大数据检索难题
|
2月前
|
数据处理 Python
Python 高级技巧:深入解析读取 Excel 文件的多种方法
在数据分析中,从 Excel 文件读取数据是常见需求。本文介绍了使用 Python 的三个库:`pandas`、`openpyxl` 和 `xlrd` 来高效处理 Excel 文件的方法。`pandas` 提供了简洁的接口,而 `openpyxl` 和 `xlrd` 则针对不同版本的 Excel 文件格式提供了详细的数据读取和处理功能。此外,还介绍了如何处理复杂格式(如合并单元格)和进行性能优化(如分块读取)。通过这些技巧,可以轻松应对各种 Excel 数据处理任务。
238 16
|
2月前
|
前端开发 JavaScript
💥【exceljs】纯前端如何实现Excel导出下载和上传解析?
本文介绍了用于处理Excel文件的库——ExcelJS,相较于SheetJS,ExcelJS支持更高级的样式自定义且易于使用。表格对比显示,ExcelJS在样式设置、内存效率及流式操作方面更具优势。主要适用于Node.js环境,也支持浏览器端使用。文中详细展示了如何利用ExcelJS实现前端的Excel导出下载和上传解析功能,并提供了示例代码。此外,还提供了在线调试的仓库链接和运行命令,方便读者实践。
370 5
|
1月前
|
数据管理 Nacos 开发者
"Nacos架构深度解析:一篇文章带你掌握业务层四大核心功能,服务注册、配置管理、元数据与健康检查一网打尽!"
【10月更文挑战第23天】Nacos 是一个用于服务注册发现和配置管理的平台,支持动态服务发现、配置管理、元数据管理和健康检查。其业务层包括服务注册与发现、配置管理、元数据管理和健康检查四大核心功能。通过示例代码展示了如何在业务层中使用Nacos,帮助开发者构建高可用、动态扩展的微服务生态系统。
92 0
|
2月前
|
SQL 分布式计算 大数据
大数据-97 Spark 集群 SparkSQL 原理详细解析 Broadcast Shuffle SQL解析过程(一)
大数据-97 Spark 集群 SparkSQL 原理详细解析 Broadcast Shuffle SQL解析过程(一)
57 0
|
2月前
|
SQL 分布式计算 算法
大数据-97 Spark 集群 SparkSQL 原理详细解析 Broadcast Shuffle SQL解析过程(二)
大数据-97 Spark 集群 SparkSQL 原理详细解析 Broadcast Shuffle SQL解析过程(二)
85 0
|
4月前
|
存储 机器学习/深度学习 数据采集
深入解析大数据核心概念:数据平台、数据中台、数据湖与数据仓库的异同与应用
深入解析大数据核心概念:数据平台、数据中台、数据湖与数据仓库的异同与应用
|
4月前
|
机器学习/深度学习 人工智能 自然语言处理
【热门开源项目】阿里开源巨擘:Qwen-2 72B深度解析与推荐
在人工智能的浪潮中,开源模型如同璀璨的星辰,指引着开发者们探索未知的领域。而今天,我们将聚焦在阿里云推出的开源模型Qwen-2 72B上,从其项目介绍、技术特点、代码解析等多个角度,深入解析并推荐这一卓越的开源项目。
196 1
|
4月前
|
存储 缓存 NoSQL
深入解析Memcached:内部机制、存储结构及在大数据中的应用
深入解析Memcached:内部机制、存储结构及在大数据中的应用

推荐镜像

更多