Java POI 百万规模数据的导入和导出

news2024/11/15 19:50:31

目录

  • 1、百万数据导入
    • 1.1 需求分析
    • 1.2 思路分析
    • 1.3 代码实现
      • 1.3.1 步骤分析
      • 1.3.2 自定义处理器
      • 1.3.3 自定义解析
      • 1.3.4 测试
  • 2、百万数据导出
    • 2.1、概述
    • 2.2、解决方案分析
    • 2.3、原理分析
    • 2.4、百万数据的导出
      • 2.4.1、模拟数据
      • 2.4.2、思路分析
      • 2.4.3、代码实现
      • 2.4.4、测试结果

1、百万数据导入

1.1 需求分析

使用POI基于事件模式解析案例提供的Excel文件

1.2 思路分析

**用户模式:**加载并读取Excel时,是通过一次性的将所有数据加载到内存中再去解析每个单元格内容。当Excel数据量较大时,由于不同的运行环境可能会造成内存不足甚至OOM异常。

例如读取我们刚刚导出的百万数据:

package com.itheima.test;

import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

//测试百万数据的导入
public class POIDemo5 {
    public static void main(String[] args) throws Exception {
        XSSFWorkbook workbook = new XSSFWorkbook("C:\\Users\\syl\\Desktop\\百万用户数据的导出.xlsx");
        XSSFSheet sheetAt = workbook.getSheetAt(0);
        String stringCellValue = sheetAt.getRow(0).getCell(0).getStringCellValue();
        System.out.println(stringCellValue);
    }
}

会直接报内存溢出的错误:
在这里插入图片描述

事件模式: 它逐行扫描文档,一边扫描一边解析。由于应用程序只是在读取数据时检查数据,因此不需要将数据存储在内存中,这对于大型文档的解析是个巨大优势。

1.3 代码实现

1.3.1 步骤分析

(1)设置POI的事件模式
根据Excel获取文件流
根据文件流创建OPCPackage 用来组合读取到的xml 组合出来的数据占用的空间更小
创建XSSFReader对象
(2)Sax解析
自定义Sheet处理器
创建Sax的XmlReader对象
设置Sheet的事件处理器
逐行读取

1.3.2 自定义处理器

package com.itheima.test;

import com.itheima.pojo.User;
import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler;
import org.apache.poi.xssf.usermodel.XSSFComment;

public class SheetHandler implements XSSFSheetXMLHandler.SheetContentsHandler {

//    编号 用户名  手机号  入职日期 现住址
    private User user=null;
    @Override
    public void startRow(int rowIndex) { //每一行的开始   rowIndex代表的是每一个sheet的行索引
        if(rowIndex==0){
            user = null;
        }else{
            user = new User();
        }
    }
    @Override  //处理每一行的所有单元格
    public void cell(String cellName, String cellValue, XSSFComment comment) {

        if(user!=null){
            String letter = cellName.substring(0, 1);  //每个单元名称的首字母 A  B  C
            switch (letter){
                case "A":{
                    user.setId(Long.parseLong(cellValue));
                    break;
                }
                case "B":{
                    user.setUserName(cellValue);
                    break;
                }
            }
        }
    }
    @Override
    public void endRow(int rowIndex) { //每一行的结束
        if(rowIndex!=0){
            System.out.println(user);
        }

    }
}

1.3.3 自定义解析

package com.itheima.test;

import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.openxml4j.opc.PackageAccess;
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler;
import org.apache.poi.xssf.model.SharedStringsTable;
import org.apache.poi.xssf.model.StylesTable;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;

import java.io.InputStream;

/**
 * 自定义Excel解析器
 */
public class ExcelParser {

    public void parse (String path) throws Exception {
        //1.根据Excel获取OPCPackage对象
        OPCPackage pkg = OPCPackage.open(path, PackageAccess.READ);
        try {
            //2.创建XSSFReader对象
            XSSFReader reader = new XSSFReader(pkg);
            //3.获取SharedStringsTable对象
            SharedStringsTable sst = reader.getSharedStringsTable();
            //4.获取StylesTable对象
            StylesTable styles = reader.getStylesTable();
            XMLReader parser = XMLReaderFactory.createXMLReader();
            // 处理公共属性:Sheet名,Sheet合并单元格
            parser.setContentHandler(new XSSFSheetXMLHandler(styles,sst, new SheetHandler(), false));
            XSSFReader.SheetIterator sheets = (XSSFReader.SheetIterator) reader.getSheetsData();
            while (sheets.hasNext()) {
                InputStream sheetstream = sheets.next();
                InputSource sheetSource = new InputSource(sheetstream);
                try {
                    parser.parse(sheetSource);
                } finally {
                    sheetstream.close();
                }
            }
        } finally {
            pkg.close();
        }
    }
}

1.3.4 测试

用户模式下读取测试Excel文件直接内存溢出,测试Excel文件映射到内存中还是占用了不少内存;事件模式下可以流畅的运行。

使用事件模型解析

public class POIDemo5 {
    public static void main(String[] args) throws Exception{
        new ExcelParser().parse("C:\\Users\\syl\\Desktop\\百万用户数据的导出.xlsx");
    }
}

2、百万数据导出

2.1、概述

​ 我们都知道Excel可以分为早期的Excel2003版本(使用POI的HSSF对象操作)和Excel2007版本(使用POI的XSSF操作),两者对百万数据的支持如下:
​ Excel 2003:在POI中使用HSSF对象时,excel 2003最多只允许存储65536条数据,一般用来处理较少的数据量。这时对于百万级别数据,Excel肯定容纳不了。
​ Excel 2007:当POI升级到XSSF对象时,它可以直接支持excel2007以上版本,因为它采用ooxml格式。这时excel可以支持1048576条数据,单个sheet表就支持近百万条数据。但实际运行时还可能存在问题,原因是执行POI报表所产生的行对象,单元格对象,字体对象,他们都不会销毁,这就导致OOM的风险。

2.2、解决方案分析

​ 对于百万数据量的Excel导入导出,只讨论基于Excel2007的解决方法。在ApachePoi 官方提供了对操作大数据量的导入导出的工具和解决办法,操作Excel2007使用XSSF对象,可以分为三种模式:

java代码解析xml

dom4j:一次性加载xml文件再解析

SAX:逐行加载,逐行解析

**用户模式:**用户模式有许多封装好的方法操作简单,但创建太多的对象,非常耗内存(之前使用的方法)

**事件模式:**基于SAX方式解析XML,SAX全称Simple API for XML,它是一个接口,也是一个软件包。它是一种XML解析的替代方法,不同于DOM解析XML文档时把所有内容一次性加载到内存中的方式,它逐行扫描文档,一边扫描,一边解析。

SXSSF对象:是用来生成海量excel数据文件,主要原理是借助临时存储空间生成excel

2.3、原理分析

在实例化SXSSFWorkBook这个对象时,可以指定在内存中所产生的POI导出相关对象的数量(默认100),一旦内存中的对象的个数达到这个指定值时,就将内存中的这些对象的内容写入到磁盘中(XML的文件格式),就可以将这些对象从内存中销毁,以后只要达到这个值,就会以类似的处理方式处理,直至Excel导出完成。

2.4、百万数据的导出

2.4.1、模拟数据

第一步、创建表

CREATE TABLE `tb_user2` (
  `id` bigint(20) NOT NULL  COMMENT '用户ID',
  `user_name` varchar(100) DEFAULT NULL COMMENT '姓名',
  `phone` varchar(15) DEFAULT NULL COMMENT '手机号',
  `province` varchar(50) DEFAULT NULL COMMENT '省份',
  `city` varchar(50) DEFAULT NULL COMMENT '城市',
  `salary` int(10) DEFAULT NULL,
  `hire_date` datetime DEFAULT NULL COMMENT '入职日期',
  `dept_id` bigint(20) DEFAULT NULL COMMENT '部门编号',
  `birthday` datetime DEFAULT NULL COMMENT '出生日期',
  `photo` varchar(200) DEFAULT NULL COMMENT '照片路径',
  `address` varchar(300) DEFAULT NULL COMMENT '现在住址' 
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

第二步、创建存储过程

DELIMITER $$    -- 重新定义“;”分号
DROP PROCEDURE IF EXISTS test_insert $$   -- 如果有test_insert这个存储过程就删除
CREATE PROCEDURE test_insert()			  -- 创建存储过程

BEGIN
   DECLARE n int DEFAULT 1;				    -- 定义变量n=1
   SET AUTOCOMMIT=0;						    -- 取消自动提交
   
   	while n <= 5000000 do					
   		INSERT INTO `tb_user2` VALUES ( n, CONCAT('测试', n), '13800000001', '北京市', '北京市', '11000', '2001-03-01 21:18:29', '1', '1981-03-02 00:00:00', '\\static\\user_photos\\1.jpg', '北京市西城区宣武大街1号院');
   		SET n=n+1;
   	END while;
   	COMMIT;
END $$

第三步、开始执行

CALL test_insert();

插入500W数据大概需要200至300秒左右

2.4.2、思路分析

导出时使用的是SXSSFWorkBook这个类,一个工作表sheet最多只能放1048576行数据, 当我们的业务数据已超过100万了,一个sheet就不够用了,必须拆分到多个工作表。

导出百万数据时有两个弊端:

1、不能使用模板

2、不能使用太多的样式

也就是说导出的数据太多时必须要放弃一些。

2.4.3、代码实现

UserController代码

@GetMapping(value = "/downLoadMillion",name = "导出用户百万数据的导出")
public void downLoadMillion(Long id,HttpServletRequest request,HttpServletResponse response) throws Exception{
    userService.downLoadMillion(request,response);
}

UserService代码

public void downLoadMillion(HttpServletRequest request, HttpServletResponse response) throws Exception {
//        创建一个空的工作薄
        Workbook workbook = new SXSSFWorkbook();
        int page = 1;
        int pageSize=200000;
        int rowIndex = 1; //每一个工作页的行数
        int num = 0; //总数据量
        Row row = null;
        Cell cell = null;
        Sheet sheet = null;
        while (true){  //不停地查询
             List<User> userList = this.findPage(page,pageSize);
             if(CollectionUtils.isEmpty(userList)){  //如果查询不到就不再查询了
                 break;
             }
            if(num%1000000==0){  //每100W个就重新创建新的sheet和标题
                rowIndex = 1;
                 //        在工作薄中创建一个工作表
                 sheet = workbook.createSheet("第"+((num/1000000)+1)+"个工作表");
//        设置列宽
                 sheet.setColumnWidth(0,8*256);
                 sheet.setColumnWidth(1,12*256);
                 sheet.setColumnWidth(2,15*256);
                 sheet.setColumnWidth(3,15*256);
                 sheet.setColumnWidth(4,30*256);
                 //            处理标题
                 String[] titles = new String[]{"编号","姓名","手机号","入职日期","现住址"};
                //        创建标题行
                Row titleRow = sheet.createRow(0);

                for (int i = 0; i < titles.length; i++) {
                    cell = titleRow.createCell(i);
                    cell.setCellValue(titles[i]);
                }
             }

//        处理内容

        for (User user : userList) {
            row = sheet.createRow(rowIndex);
            cell = row.createCell(0);
            cell.setCellValue(user.getId());

            cell = row.createCell(1);
            cell.setCellValue(user.getUserName());

            cell = row.createCell(2);
            cell.setCellValue(user.getPhone());

            cell = row.createCell(3);
            cell.setCellValue(simpleDateFormat.format(user.getHireDate()));

            cell = row.createCell(4);
            cell.setCellValue(user.getAddress());
            rowIndex++;
            num++;
        }
          page++;// 继续查询下一页
        }
//            导出的文件名称
        String filename="百万数据.xlsx";
//            设置文件的打开方式和mime类型
        ServletOutputStream outputStream = response.getOutputStream();
        response.setHeader( "Content-Disposition", "attachment;filename="  + new String(filename.getBytes(),"ISO8859-1"));
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        workbook.write(outputStream);
    }

2.4.4、测试结果

导出的这个文档大概需要3-5分钟的时间,有105 MB,内容如下

在这里插入图片描述

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

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

相关文章

面试题:请说下什么是重绘和重排(回流)?他们的区别是什么?

答&#xff1a; ● 第一次渲染 ○ html结构 解析为 dom树 ○ css样式 解析为 样式规则 ○ dom树 和 样式规则 匹配下&#xff0c;生成渲染树&#xff01; ○ 接下来就是重排&#xff1a;根据渲染树&#xff0c;得到每个盒子的几何信息&#xff08;大小位置&#xff09; ○ 最后…

html纯前端压缩图片神器,js压缩图片保持原有像素,html图片压缩自定义大小,html/js实现图片压缩

前言 纯前端实现&#xff0c;关键使用image和canvas容器 使用jquery和layui方便简写 可自定义压缩质量、缩放大小、图片类型 打开index.html即可使用 效果 实现&#xff1a;https://download.csdn.net/download/weixin_43992507/88158384

springboot访问请求404的原因

是记录&#xff0c;可能出现错误 可能出现的原因 1.你请求的URL路径不对,比如说你请求的路径是/usr/list,GET方法,但是你UserController上面的RequestMapping是这个样子:RequestMapping(“user”)&#xff0c;有可能哈 2.前端的请求时GET方法&#xff0c;后端对应的处理函数的方…

中国篆刻艺术孙溟㠭作品《活着》

人人为生活挣扎着&#xff0c;做着不想做的事&#xff0c;说着不想说的话&#xff0c;为生活低头弯腰委屈求全人生苦多甜少&#xff0c;何时了&#xff01;何时了&#xff01;甜来人生到头了…… 孙溟㠭篆刻作品《活着》 孙溟㠭篆刻作品《活着》 孙溟㠭篆刻作品《活着》 文/…

SAP从放弃到入门系列之创建特殊库存转储预留

文章概览 一、思路二、过程2.1前台的主要过程&#xff1a;2.2 BAPI的实现过程&#xff1a; 之前写过几篇生产领料的思路包括代码&#xff0c;有兴趣的可以翻翻之前我发的文章。最近遇到既有项目专用物资、按单专用物资、通用物资合并领料的业务模式&#xff0c;所以领料的库存的…

《Java-SE-第二十七章》之常见的锁策略

前言 在你立足处深挖下去,就会有泉水涌出!别管蒙昧者们叫嚷:“下边永远是地狱!” 博客主页&#xff1a;KC老衲爱尼姑的博客主页 博主的github&#xff0c;平常所写代码皆在于此 共勉&#xff1a;talk is cheap, show me the code 作者是爪哇岛的新手&#xff0c;水平很有限&…

【腾讯云 Cloud studio 实战训练营】云端 IDE 构建移动端H5

&#x1f431; 个人主页&#xff1a;不叫猫先生&#xff0c;公众号&#xff1a;前端舵手 &#x1f64b;‍♂️ 作者简介&#xff1a;2022年度博客之星前端领域TOP 2&#xff0c;前端领域优质作者、阿里云专家博主&#xff0c;专注于前端各领域技术&#xff0c;共同学习共同进步…

【SpringBoot】有哪些优点+配置文件如何配置?

博主简介&#xff1a;想进大厂的打工人博主主页&#xff1a;xyk:所属专栏: JavaEE进阶 Spring 的诞⽣是为了简化 Java 程序的开发的&#xff0c;⽽ Spring Boot 的诞⽣是为了简化 Spring 程序开发 的。Spring Boot是一个开源的Java框架&#xff0c;用于快速构建应用程序和微服…

如何将文件写入数据库呢???(走过路过不要错过)

1.首先建立数据库。。。建立一个名为books的数据库&#xff0c;建立一个fs表。 create database if not exists books; use books; create table fs(id int unsigned auto_increment primary key ,name varchar(50) not null ,files longblob ); 假如你不喜欢代码建立&#x…

redux-promise-middleware和applyMiddleware的理解与使用

一、作用&#xff1a; applyMiddleware是一个中间件&#xff0c;通常和applyMiddleware结合使用&#xff0c;是dispatch与reducers之间的应用&#xff0c;用于处理dispatch发送的异步action操作 二、使用 1、安装redux-promise-middleware cnpm i redux-promise-middleware…

GB28181智能安全帽方案探究及技术实现

什么是智能安全帽&#xff1f;​ 智能安全帽是一种集成先进科技的安全帽&#xff0c;可基于GB28181规范&#xff0c;适用于铁路巡检、电力、石油化工等高风险行业的作业人员&#xff0c;以及消防、救援等紧急情况下的安全防护。 智能安全帽通常具有以下功能&#xff1a; 实时…

WEB开发的基础知识

WEB开发的基础知识 1、java SE 和 java EE 2、web开发的基本理念 3、URI和URL 4、http请求方式 5、get和post的区别 6、web开发行业术语

hdu Perfect square number

题意&#xff1a; 有n个数&#xff08;n<300&#xff09;&#xff0c;将其中的任意的一个数改为x&#xff08;x在[1,300]&#xff09;&#xff0c;求改之后&#xff0c;区间和为完全平方数的最大区间个数是多少 思路&#xff1a; 将a[x]改之后的区间个数等于&#xff1a;改…

ThinkPHP v6.0.8 CacheStore 反序列化漏洞

漏洞说明 1. 漏洞原理&#xff1a;ThinkPHP 6.0.8 CacheStore 会触发POP利用链子&#xff0c;造成任意命令执行 2. 组件描述&#xff1a; ThinkPHP是一个免费开源的&#xff0c;快速、简单的面向对象的轻量级PHP开发框架 3. 影响版本&#xff1a;V6.0.8 漏洞复现 1. 环境安…

【Linux命令200例】which用于查找指定命令所在路径

&#x1f3c6;作者简介&#xff0c;黑夜开发者&#xff0c;全栈领域新星创作者✌&#xff0c;阿里云社区专家博主&#xff0c;2023年6月csdn上海赛道top4。 &#x1f3c6;本文已收录于专栏&#xff1a;Linux命令大全。 &#x1f3c6;本专栏我们会通过具体的系统的命令讲解加上鲜…

【C++】STL——queue的介绍和使用、queue的push和pop函数介绍和使用、queue的其他成员函数

文章目录 1.queue的介绍2.queue的使用2.1queue构造函数2.2queue的成员函数&#xff08;1&#xff09;empty() 检测队列是否为空&#xff0c;是返回true&#xff0c;否则返回false&#xff08;2&#xff09;size() 返回队列中有效元素的个数 &#xff08;3&#xff09;front() 返…

PHP从入门到精通—PHP开发入门-PHP概述、PHP开发环境搭建、PHP开发环境搭建、第一个PHP程序、PHP开发流程

每开始学习一门语言&#xff0c;都要了解这门语言和进行开发环境的搭建。同样&#xff0c;学生开始PHP学习之前&#xff0c;首先要了解这门语言的历史、语言优势等内容以及了解开发环境的搭建。 PHP概述 认识PHP PHP最初是由Rasmus Lerdorf于1994年为了维护个人网页而编写的一…

【C语言学习】数据类型转换

一、自动类型转换 1.当运算符两边的数据类型不同时&#xff0c;C语言会帮我们将其转换为较大的类型。即将数据转换成表达范围更大的类型。 将前一种类型转换为后一种类型 char --> short --> int --> long --> long long int --> float --> double2.对于…

数据结构:插入排序

直接插入排序 插入排序算法是所有排序方法中最简单的一种算法&#xff0c;其主要的实现思想是将数据按照一定的顺序一个一个的插入到有序的表中&#xff0c;最终得到的序列就是已经排序好的数据。 直接插入排序是插入排序算法中的一种&#xff0c;采用的方法是&#xff1a;在…

网盘共享文件的优势及对团队办公的帮助

伴随着科技的发展&#xff0c;互联网逐步渗透了企业办公方式。各种类型的网盘应运而生&#xff0c;成为当下文件共享的主要方式之一。那么网盘共享文件有什么优势&#xff1f;对团队办公有何帮助呢&#xff1f; 网盘共享文件的优势 1、方便快捷&#xff1a;用户通过移动设备即…