Excel学习

发布时间:2024年01月13日

学习链接

EasyExcel实现Excel文件导入导出 - 很详细

EasyPoi实现excel文件导入导出 - 很详细

Excel文件导入导出 - 专栏

【狂神说Java】POI技术详解

Excel

在企业级应用开发中,Excel报表是一种最常见的报表需求。Excel报表开发一般分为两种形式:

  • 为了方便操作,基于Excel的报表批量上传数据
  • 通过java代码生成Excel报表。

1. Excel的两种形式

目前世面上的Excel分为两个大的版本Excel2003和Excel2007及以上两个版本,两者之间的区别如下:

在这里插入图片描述

Excel2003是一个特有的二进制格式,其核心结构是复合文档类型的结构,存储数据量较小;Excel2007 的核心结构是 XML 类型的结构,采用的是基于 XML 的压缩方式,使其占用的空间更小,操作效率更高

2. 常见excel操作工具

Java中常见的用来操作Excl的方式一般有2种:JXL和POI。

  • JXL只能对Excel进行操作,属于比较老的框架,它只支持到Excel 95-2000的版本。现在已经停止更新和维护。
  • POI是apache的项目,可对微软的Word,Excel,Ppt进行操作,包括office2003和2007,Excl2003和2007。poi现在一直有更新。所以现在主流使用POI。

3.POI

1. POI的概述

Apache POI是Apache软件基金会的开源项目,由Java编写的免费开源的跨平台的 Java API,Apache POI提供API给Java语言操作Microsoft Office的功能。

2. POI的应用场景
  1. 数据报表生成
  2. 数据备份
  3. 数据批量上传
3. 使用

导入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
                             http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.itcast</groupId>
    <artifactId>poi-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>4.0.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>4.0.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml-schemas</artifactId>
            <version>4.0.1</version>
        </dependency>
    </dependencies>
</project>
1.使用POI创建excel
/**
 * 使用POI创建excel
 */
public class PoiTest01 {


    public static void main(String[] args) throws Except   ion {
        //1.创建工作簿  HSSFWorkbook -- 2003
        Workbook wb = new XSSFWorkbook(); //2007版本
        //2.创建表单sheet
        Sheet sheet = wb.createSheet("test");
        //3.文件流
        FileOutputStream fos = new FileOutputStream("E:\\excel\\poi\\test.xlsx");
        //4.写入文件
        wb.write(fos);
        fos.close();
    }
}
2.创建单元格写入内容
/**
 * 创建单元格写入内容
 */
public class PoiTest02 {


    public static void main(String[] args) throws Exception {
        //创建工作簿  HSSFWorkbook -- 2003
        Workbook wb = new XSSFWorkbook(); //2007版本
        //创建表单sheet
        Sheet sheet = wb.createSheet("test");
        //创建行对象  参数:索引(从0开始)
        Row row = sheet.createRow(2);
        //创建单元格对象  参数:索引(从0开始)
        Cell cell = row.createCell(2);
        //向单元格中写入内容
        cell.setCellValue("传智播客");
        //文件流
        FileOutputStream pis = new FileOutputStream("E:\\excel\\poi\\test1.xlsx");
        //写入文件
        wb.write(pis);
        pis.close();
    }
}
3.单元格样式处理
/**
 * 单元格样式处理
 */
public class PoiTest03 {


    public static void main(String[] args) throws Exception {
        //创建工作簿  HSSFWorkbook -- 2003
        Workbook wb = new XSSFWorkbook(); //2007版本
        //创建表单sheet
        Sheet sheet = wb.createSheet("test");
        //创建行对象  参数:索引(从0开始)
        Row row = sheet.createRow(2);
        //创建单元格对象  参数:索引(从0开始)
        Cell cell = row.createCell(2);
        //向单元格中写入内容
        cell.setCellValue("传智播客");

        //样式处理
        //创建样式对象
        CellStyle style = wb.createCellStyle();
        style.setBorderTop(BorderStyle.THIN);//上边框
        style.setBorderBottom(BorderStyle.THIN);//下边框
        style.setBorderLeft(BorderStyle.THIN);//左边框
        style.setBorderRight(BorderStyle.THIN);//右边框
        //创建字体对象
        Font font = wb.createFont();
        font.setFontName("华文行楷"); //字体
        font.setFontHeightInPoints((short)28);//字号
        style.setFont(font);

        //行高和列宽
        row.setHeightInPoints(50);//行高
        //列宽的宽度  字符宽度
        sheet.setColumnWidth(2, 31 * 256);//列宽

        //居中显示
        style.setAlignment(HorizontalAlignment.CENTER);//水平居中
        style.setVerticalAlignment(VerticalAlignment.CENTER);//垂直居中

        //向单元格设置样式
        cell.setCellStyle(style);

        //文件流
        FileOutputStream pis = new FileOutputStream("E:\\excel\\poi\\test2.xlsx");
        //写入文件
        wb.write(pis);
        pis.close();
    }
}
4.插入图片
/**
 * 插入图片
 */
public class PoiTest04 {


    public static void main(String[] args) throws Exception {
        //创建工作簿  HSSFWorkbook -- 2003
        Workbook wb = new XSSFWorkbook(); //2007版本
        //创建表单sheet
        Sheet sheet = wb.createSheet("test");

        //读取图片流
        FileInputStream stream = new FileInputStream("E:\\excel\\poi\\logo.jpg");
        //转化二进制数组
        byte[] bytes = IOUtils.toByteArray(stream);
        stream.read(bytes);
        //向POI内存中添加一张图片,返回图片在图片集合中的索引
        //参数一:图片的二进制数据,参数二:图片类型
        int index = wb.addPicture(bytes, Workbook.PICTURE_TYPE_JPEG);
        //绘制图片工具类
        CreationHelper helper = wb.getCreationHelper();
        //创建一个绘图对象
        Drawing<?> patriarch = sheet.createDrawingPatriarch();

        //创建锚点,设置图片坐标
        ClientAnchor anchor = helper.createClientAnchor();
        anchor.setRow1(0);
        anchor.setCol1(0);

        /*
        // 如下设置可以让图片限制在指定的单元格内
        // 位置  后四个参数:   前两个: 图片左上角的X,Y坐标     后两个:图片右下左上角的X,Y坐标
            XSSFClientAnchor anchor = new XSSFClientAnchor(0, 0, 0, 0, 1, 1, 5, 8);
            anchor.setAnchorType(ClientAnchor.AnchorType.DONT_MOVE_AND_RESIZE);
        */

        //绘制图片
        Picture picture = patriarch.createPicture(anchor, index);//图片位置,图片的索引
        picture.resize();//自适应渲染图片

        //文件流
        FileOutputStream fos = new FileOutputStream("E:\\excel\\poi\\test3.xlsx");
        //写入文件
        wb.write(fos);
        fos.close();
    }
}
5.读取excel并解析
/**
 * 读取excel并解析
 *      sheet.getLastRowNum() : 最后一行的索引(从0开始数)
 *      row.getLastCellNum() : 最后一个单元格的号码(从1开始数)
 */
public class PoiTest05 {

    public static void main(String[] args) throws Exception {
        
        // 如果excel文件过大报错,可使用下面这句代码
        ZipSecureFile.setMinInflateRatio(-1.0d);
        
        //1.根据Excel文件创建工作簿
        Workbook wb = new XSSFWorkbook("E:\\excel\\poi\\demo.xlsx");
        
        //2.获取Sheet
        Sheet sheet = wb.getSheetAt(0);//参数:索引
        
        //3.获取Sheet中的每一行,和每一个单元格(行是按索引来的)
        for (int rowNum = 0; rowNum<= sheet.getLastRowNum() ;rowNum ++) {
            
            Row row = sheet.getRow(rowNum);//根据索引获取每一个行
            
            StringBuilder sb = new StringBuilder();
            
            // (这里返回的最后一列不是按索引,是从1开始)
            for(int cellNum=2;cellNum< row.getLastCellNum(); cellNum ++) {
                
                //根据索引获取每一个单元格
                Cell cell = row.getCell(cellNum);
                //获取每一个单元格的内容
                Object value = getCellValue(cell);
                sb.append(value).append("-");
            }
            System.out.println(sb.toString());
        }
    }

    public static Object getCellValue(Cell cell) {
        //1.获取到单元格的属性类型
        CellType cellType = cell.getCellType();
        //2.根据单元格数据类型获取数据
        Object value = null;
        switch (cellType) {
            case STRING:
                value = cell.getStringCellValue();
                break;
            case BOOLEAN:
                value = cell.getBooleanCellValue();
                break;
            case NUMERIC:
                if(DateUtil.isCellDateFormatted(cell)) {
                    //日期格式
                    value = cell.getDateCellValue();
                }else{
                    //数字
                    value = cell.getNumericCellValue();
                }
                break;
            case FORMULA: //公式
                value = cell.getCellFormula();
                break;
            default:
                break;
        }
        return value;
    }
}

// poi将会把单元格内的数字都会认为是double类型,所以需要转换
public static void main(String[] args) {
    
    DecimalFormat decimalFormat1 = new DecimalFormat("#");
    DecimalFormat decimalFormat2 = new DecimalFormat("#.#");
    DecimalFormat decimalFormat3 = new DecimalFormat("#.##");
    DecimalFormat decimalFormat4 = new DecimalFormat("#.###");
    
    String format1 = decimalFormat1.format(4.25); // 这里接的参数是Object类型
    String format2 = decimalFormat2.format(4.25);
    String format3 = decimalFormat3.format(4.25);
    String format4 = decimalFormat4.format(4.25);
    
    System.out.println(format1); // 4
    System.out.println(format2); // 4.2
    System.out.println(format3); // 4.25
    System.out.println(format4); // 4.25

    System.out.println(((Double)4.25).intValue()); // 4
}
图解POI

在这里插入图片描述

4. 基于模板输出POI报表

模板如下

在这里插入图片描述

代码如下

// 读取类路径下的hr-demo.xlsx的模板Excel文件的样式,然后读取原有的样式并在写入数据的时候应用原有的样式
@Test
public void test_template() throws IOException, InvalidFormatException {

    // 读取模板的数据样式
    ClassPathResource resource = new ClassPathResource("hr-demo.xlsx");
    XSSFWorkbook workbook = new XSSFWorkbook(resource.getFile());

    XSSFSheet sheet = workbook.getSheetAt(0);
    XSSFRow row = sheet.getRow(2);
    short defaultRowHeight = row.getHeight(); // 获取行高
    CellStyle[] cellStyles = new CellStyle[row.getLastCellNum()];
    for (int i = 0; i < cellStyles.length; i++) {
        XSSFCell cell = row.getCell(i);
        cellStyles[i] = cell.getCellStyle();
    }


    // 使用读取到的模板样式,写入数据
    // 第一步:修改标题
    XSSFRow row0 = sheet.getRow(0);
    XSSFCell cell00 = row0.getCell(0);
    cell00.setCellValue("xx年xx月xx日人事报表");

    ArrayList<User> users = new ArrayList<>();
    users.add(new User("zj", 25));
    users.add(new User("zzhua", 23));

    int startRow = 2;
    for (User user : users) {
        XSSFRow r = sheet.createRow(startRow++);
        r.setHeight(defaultRowHeight);
        XSSFCell cell = r.createCell(0);
        for (int i = 0; i < 3; i++) {
            XSSFCell iCell = r.createCell(i);
            iCell.setCellStyle(cellStyles[i]);
            if (i == 0) {
                iCell.setCellValue(startRow-1);
            } else if (i == 1) {
                iCell.setCellValue(user.getName());
            } else {
                iCell.setCellValue(user.getAge());
            }
        }
    }

    FileOutputStream fos = new FileOutputStream("D:\\Projects\\practice\\poi\\poi"
                                                +"demo\\src\\main\\resources\\test.xlsx");
    workbook.write(fos);
    fos.flush();
    fos.close();
}
5. 自定义POI导出工具类
ExcelAttribute
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ExcelAttribute {
    /** 对应的列名称 */
    String name() default "";

    /** 列序号 */
    int sort();

    /** 字段类型对应的格式 */
    String format() default "";

}
ExcelExportUtil
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

public class ExcelExportUtil<T> {

    private int rowIndex;        // 数据开始行

    private int styleIndex;      // 数据的样式开始行
    private String templatePath; // excel模板的路径
    private Class clazz;         // 实体类
    private  Field fields[];     // 实体类的字段

    public ExcelExportUtil(Class clazz,int rowIndex,int styleIndex) {
        this.clazz = clazz;
        this.rowIndex = rowIndex;
        this.styleIndex = styleIndex;
        fields = clazz.getDeclaredFields();
    }

    /**
     * 基于注解导出
     */
    public void export(HttpServletResponse response,InputStream is, List<T> objs, 
                       String fileName) throws Exception {

        // 拿到workbook,并获取到第一页
        XSSFWorkbook workbook = new XSSFWorkbook(is);
        Sheet sheet = workbook.getSheetAt(0);

        // 获取样式行,并提取所有样式
        Row styleRow = sheet.getRow(styleIndex);
        CellStyle[] styles = getTemplateStyles(styleRow);
        // 获取样式行的行高
        short defaultHeight = styleRow.getHeight();

        // 从要写的数据行开始
        AtomicInteger datasAi = new AtomicInteger(rowIndex);

        // 遍历所有的数据
        for (T t : objs) {
            // 拿到一条待写入的对象,并且遍历样式就行了
            Row row = sheet.createRow(datasAi.getAndIncrement()); // 相当于后++
            // 设置行高
            row.setHeight(defaultHeight);
            for(int i=0;i<styles.length;i++) {
                Cell cell = row.createCell(i);
                // 设置提取的样式
                cell.setCellStyle(styles[i]);
                // 根据每一列的索引匹配字段(后面可以优化下,将字段放入map,然后去找字段)
                for (Field field : fields) {
                    if(field.isAnnotationPresent(ExcelAttribute.class)){
                        field.setAccessible(true);
                        ExcelAttribute ea = field.getAnnotation(ExcelAttribute.class);
                        if(i == ea.sort()) {
                            cell.setCellValue(field.get(t).toString());
                        }
                    }
                }
            }
        }

        fileName = URLEncoder.encode(fileName, "UTF-8");
        response.setContentType("application/octet-stream");
        response.setHeader("content-disposition", "attachment;filename=" 
                           + new String(fileName.getBytes("ISO8859-1")));
        response.setHeader("filename", fileName);
        workbook.write(response.getOutputStream());
    }

    public CellStyle[] getTemplateStyles(Row row) {
        CellStyle [] styles = new CellStyle[row.getLastCellNum()];
        for(int i=0;i<row.getLastCellNum();i++) {
            styles[i] = row.getCell(i).getCellStyle();
        }
        return styles;
    }
}
ExcelImportUtil
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import java.io.InputStream;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;


public class ExcelImportUtil<T> {

    // 实体类
    private Class clazz;
    // 实体类的字段
    private  Field fields[];

    public ExcelImportUtil(Class clazz) {
        this.clazz = clazz;
        fields = clazz.getDeclaredFields();
    }

    /**
     * 基于注解读取excel
     */
    public List<T> readExcel(InputStream is, int rowIndex,int cellIndex) {
        List<T> list = new ArrayList<T>();
        T entity = null;
        try {
            // 获取文件,包装成WorkBook
            XSSFWorkbook workbook = new XSSFWorkbook(is);
            // 获取第一页
            Sheet sheet = workbook.getSheetAt(0);

            // 获取到最后有数据的一行的索引(从0开始)
            int rowLength = sheet.getLastRowNum();
            System.out.println(sheet.getLastRowNum());

            // rowIndex 读取数据的开始行
            for (int rowNum = rowIndex; rowNum <= sheet.getLastRowNum(); rowNum++) {
                // 获取到所在行数据
                Row row = sheet.getRow(rowNum);
                // 反射创建对象
                entity = (T) clazz.newInstance();
                // 当前行最后有数据的列数(从1开始)
                System.out.println(row.getLastCellNum());

                // cellIndex读起的列索引(因为下面用的getCell(index))
                for (int j = cellIndex; j < row.getLastCellNum(); j++) {
                    // 根据列拿到当行对应列的单元格
                    Cell cell = row.getCell(j);
                    // 遍历所有字段,找到sort与列匹配的字段,获取值,并通过反射设置给对象
                    for (Field field : fields) {
                        if(field.isAnnotationPresent(ExcelAttribute.class)){
                            field.setAccessible(true);
                            ExcelAttribute ea = field.getAnnotation(ExcelAttribute.class);
                            if(j == ea.sort()) {
                                field.set(entity, covertAttrType(field, cell));
                            }
                        }
                    }
                }
                list.add(entity);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return list;
    }


    /**
     * 类型转换 将cell 单元格格式转为 字段类型
     */
    private Object covertAttrType(Field field, Cell cell) throws Exception {
        String fieldType = field.getType().getSimpleName();
        if ("String".equals(fieldType)) {
            return getValue(cell);
        }else if ("Date".equals(fieldType)) {
            return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").parse(getValue(cell)) ;
        }else if ("int".equals(fieldType) || "Integer".equals(fieldType)) {
            return Integer.parseInt(getValue(cell));
        }else if ("double".equals(fieldType) || "Double".equals(fieldType)) {
            return Double.parseDouble(getValue(cell));
        }else {
            return null;
        }
    }


    /**
     * 格式转为String
     * @param cell
     * @return
     */
    public String getValue(Cell cell) {
        if (cell == null) {
            return "";
        }
        switch (cell.getCellType()) {
            case STRING:
                return cell.getRichStringCellValue().getString().trim();
            case NUMERIC:
                if (DateUtil.isCellDateFormatted(cell)) {
                    Date dt = DateUtil.getJavaDate(cell.getNumericCellValue());
                    return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(dt);
                } else {
                    // 防止数值变成科学计数法
                    String strCell = "";
                    Double num = cell.getNumericCellValue();
                    BigDecimal bd = new BigDecimal(num.toString());
                    if (bd != null) {
                        strCell = bd.toPlainString();
                    }
                    // 去除 浮点型 自动加的 .0
                    if (strCell.endsWith(".0")) {
                        strCell = strCell.substring(0, strCell.indexOf("."));
                    }
                    return strCell;
                }
            case BOOLEAN:
                return String.valueOf(cell.getBooleanCellValue());
            default:
                return "";
        }
    }
}
6. 百万数据报表
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. JDK性能监控工具介绍

没有性能监控工具一切推论都只能停留在理论阶段,我们可以使用Java的性能监控工具来监视程序的运行情况,包括CUP,垃圾回收,内存的分配和使用情况,这让程序的运行阶段变得更加可控,也可以用来证明我们的推测。这里我们使用JDK提供的性能工具Jvisualvm来监控程序运行。

1. Jvisualvm概述

VisualVM 是Netbeans的profile子项目,已在JDK6.0 update 7 中自带,能够监控线程,内存情况,查看方法的CPU时间和内存中的对 象,已被GC的对象,反向查看分配的堆栈

2. Jvisualvm的位置

Jvisualvm位于JAVA_HOME/bin目录下,直接双击就可以打开该程序。如果只是监控本地的java进程,是不需要配置参数的,直接打开就能够进行监控。首先我们需要在本地打开一个Java程序,例如我打开员工微服务进程,这时在jvisualvm界面就可以看到与IDEA相关的Java进程了

在这里插入图片描述

3. Jvisualvm的使用

Jvisualvm使用起来比较简单,双击点击当前运行的进程即可进入到程序的监控界面

在这里插入图片描述

  • 概述:可以看到进程的启动参数。
  • 监视:左上:cpu利用率,gc状态的监控,右上:堆利用率,永久内存区的利用率,左下:类的监控,右下:线程的监控
  • 线程:能够显示线程的名称和运行的状态,在调试多线程时必不可少,而且可以点进一个线程查看这个线程的详细运行情况
3. 百万数据导出分析

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

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

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

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

在这里插入图片描述

这是一张Apache POI官方提供的图片,描述了基于用户模式,事件模式,以及使用SXSSF三种方式操作Excel的特性以及CUP和内存占用情况。

1.从java安装的bin目录下打开jvisualvm.exe程序

在这里插入图片描述

使用XSSFWorkBook写入一百万条数据,查看堆内存变化

在这里插入图片描述

将XSSFWorkBook改为SXSSFWorkBook,启动并查看临时文件夹,有数据写入到了文件中,并且是以xml格式写入的

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

1. 思路分析

基于XSSFWork导出Excel报表,是通过将所有单元格对象保存到内存中,当所有的Excel单元格全部创建完成之后一次性写入到Excel并导出。当百万数据级别的Excel导出时,随着表格的不断创建,内存中对象越来越多,直至内存溢出。Apache Poi提供了SXSSFWork对象,专门用于处理大数据量Excel报表导出。

2. 原理分析

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

3. 代码实现

在原有代码的基础上替换之前的XSSFWorkbook,使用SXSSFWorkbook完成创建过程即可

(百万数据导出不支持模板,有待测试,因为避免创建过多的对象)

//1.构造数据
List<EmployeeReportResult> list =
    userCompanyPersonalService.findByReport(companyId,month+"%");

//2.创建工作簿
SXSSFWorkbook workbook = new SXSSFWorkbook(); // 默认100

//3.构造sheet
String[] titles = {"编号", "姓名", "手机","最高学历", "国家地区", "护照号", "籍贯",
                   "生日", "属相","入职时间","离职类型","离职原因","离职时间"};
Sheet sheet = workbook.createSheet();
Row row = sheet.createRow(0);
AtomicInteger headersAi = new AtomicInteger();
for (String title : titles) {
    Cell cell = row.createCell(headersAi.getAndIncrement());
    cell.setCellValue(title);
}
AtomicInteger datasAi = new AtomicInteger(1);
Cell cell = null;
for(int i=0;i<10000;i++) {
    for (EmployeeReportResult report : list) {
        Row dataRow = sheet.createRow(datasAi.getAndIncrement());
        //编号
        cell = dataRow.createCell(0);
        cell.setCellValue(report.getUserId());
        //姓名
        cell = dataRow.createCell(1);
        cell.setCellValue(report.getUsername());
        //手机
        cell = dataRow.createCell(2);
        cell.setCellValue(report.getMobile());
        //最高学历
        cell = dataRow.createCell(3);
        cell.setCellValue(report.getTheHighestDegreeOfEducation());
        //国家地区
        cell = dataRow.createCell(4);
        cell.setCellValue(report.getNationalArea());
        //护照号
        cell = dataRow.createCell(5);
        cell.setCellValue(report.getPassportNo());
        //籍贯
        cell = dataRow.createCell(6);
        cell.setCellValue(report.getNativePlace());
        //生日
        cell = dataRow.createCell(7);
        cell.setCellValue(report.getBirthday());
        //属相
        cell = dataRow.createCell(8);
        cell.setCellValue(report.getZodiac());
        //入职时间
        cell = dataRow.createCell(9);
        cell.setCellValue(report.getTimeOfEntry());
        //离职类型
        cell = dataRow.createCell(10);
        cell.setCellValue(report.getTypeOfTurnover());
        //离职原因
        cell = dataRow.createCell(11);
        cell.setCellValue(report.getReasonsForLeaving());
        //离职时间
        cell = dataRow.createCell(12);
        cell.setCellValue(report.getResignationTime());
    }
}
String fileName = URLEncoder.encode(month+"人员信息.xlsx", "UTF-8");
response.setContentType("application/octet-stream");
response.setHeader("content-disposition", "attachment;filename=" + new
                   String(fileName.getBytes("ISO8859-1")));
response.setHeader("filename", fileName);
workbook.write(response.getOutputStream());
4. 对比测试

XSSFWorkbook生成百万数据报表

使用XSSFWorkbook生成Excel报表,时间较长,随着时间推移,内存占用原来越多,直至内存溢出

在这里插入图片描述

SXSSFWorkbook生成百万数据报表

使用SXSSFWorkbook生成Excel报表,内存占用比较平缓

在这里插入图片描述

4.百万数据报表读取

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

1. 思路分析

用户模式:加载并读取Excel时,是通过==一次性将所有数据加载到内存中再去解析每个单元格内容==。当Excel数据量较大时,由于不同的运行环境可能会造成内存不足甚至OOM异常。
事件模式:它逐行扫描文档,一边扫描一边解析。由于应用程序只是在读取数据时检查数据,因此不需要将数据存储在内存中,这对于大型文档的解析是个巨大优势。

2. 步骤分析

1、设置POI的事件模式

根据Excel获取文件流

根据文件流创建OPCPackage

创建XSSFReader对象

2、Sax解析

自定义Sheet处理器

创建Sax的XmlReader对象

设置Sheet的事件处理器

逐行读取

3.原理分析

我们都知道对于Excel2007的实质是一种特殊的XML存储数据,那就可以使用基于SAX的方式解析XML完成Excel的读取。SAX提供了一种从XML文档中读取数据的机制。它逐行扫描文档,一边扫描一边解析。由于应用程序只是在读取数据时检查数据,因此不需要将数据存储在内存中,这对于大型文档的解析是个巨大优势

读取到某行时,调用注册的事件处理器处理数据,然后再处理下一行,同样继续调用注册的事件处理器。

在这里插入图片描述

4.代码实现

实体类

@Data
public class PoiEntity {
    private String id;
    private String breast;
    private String adipocytes;
    private String negative;
    private String staining;
    private String supportive;
}

自定义事件处理器

/**
 * 自定义的事件处理器
 *  处理每一行数据读取
 *      实现接口
 */
public class SheetHandler implements XSSFSheetXMLHandler.SheetContentsHandler {

    private PoiEntity entity;
    /**
     * 当开始解析某一行的时候触发
     *      i:行索引
     */
    @Override
    public void startRow(int i) {
        //实例化对象
        if(i>0) {
            entity = new PoiEntity();
        }
    }

    /**
     * 当结束解析某一行的时候触发
     *      i:行索引
     */
    @Override
    public void endRow(int i) {
        //使用对象进行业务操作
        System.out.println(entity);
    }

    /**
     * 对行中的每一个表格进行处理
     *      cellReference: 单元格名称
     *      value:数据
     *      xssfComment:批注
     */
    @Override
    public void cell(String cellReference, String value, XSSFComment xssfComment) {
        //对 对象属性 赋值
        if(entity != null) {
            String pix = cellReference.substring(0,1);
            switch (pix) {
                case "A":
                    entity.setId(value);
                    break;
                case "B":
                    entity.setBreast(value);
                    break;
                case "C":
                    entity.setAdipocytes(value);
                    break;
                case "D":
                    entity.setNegative(value);
                    break;
                case "E":
                    entity.setStaining(value);
                    break;
                case "F":
                    entity.setSupportive(value);
                    break;
                default:
                    break;
            }
        }
    }
}

读取百万数据的excel报表

/**
 * 使用事件模型解析百万数据excel报表
 */
public class MassivePoiTest {

    public static void main(String[] args) throws Exception {
        String path = "C:\\Users\\ThinkPad\\Desktop\\ihrm\\day8\\资源\\百万数据报表\\demo.xlsx";

        //1.根据excel报表获取OPCPackage
        OPCPackage opcPackage = OPCPackage.open(path, PackageAccess.READ);
        
        //2.创建XSSFReader
        XSSFReader reader = new XSSFReader(opcPackage);
        
        //3.获取SharedStringTable对象
        SharedStringsTable table = reader.getSharedStringsTable();
        
        //4.获取styleTable对象
        StylesTable stylesTable = reader.getStylesTable();
        
        //5.创建Sax的xmlReader对象
        XMLReader xmlReader = XMLReaderFactory.createXMLReader();
        
        //6.注册事件处理器
        XSSFSheetXMLHandler xmlHandler = new XSSFSheetXMLHandler(stylesTable,table,
                                                                 new SheetHandler(),
                                                                 false);
        xmlReader.setContentHandler(xmlHandler);
        
        //7.逐行读取
        XSSFReader.SheetIterator sheetIterator = (XSSFReader.SheetIterator) reader.getSheetsData();
        
        while (sheetIterator.hasNext()) {
            InputStream stream = sheetIterator.next(); //每一个sheet的流数据
            InputSource is = new InputSource(stream);
            xmlReader.parse(is);
        }
    }
}
5.对比测试

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

(1)使用用户模型解析

在这里插入图片描述

(2)使用事件模型解析

在这里插入图片描述

通过简单的分析以及运行两种模式进行比较,可以看到用户模式下使用更简单的代码实现了Excel读取,但是在读取大文件时CPU和内存都不理想;而事件模式虽然代码写起来比较繁琐,但是在读取大文件时CPU和内存更加占优势。

文章来源:https://blog.csdn.net/qq_16992475/article/details/135562828
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。