POI和EasyExcel

POI和easyExcel

场景:

1、将用户信息导出为excel表格

2、将excel表信息录入到网上数据库(习题上传),大大减轻录入量

内存问题:

poi=先把所有数据加载到内存再写入

easyExcel=一条一条写

excel元素

1、工作簿

2、工作表

3、行

4、列

1、POI方式实现

POI-Excel写

POI依赖

<!--        xls(03)-->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>3.9</version>
</dependency>
    
<!--        xls(07)-->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>3.9</version>
</dependency>
    
<!--        日期格式化工具-->
<dependency>
    <groupId>joda-time</groupId>
    <artifactId>joda-time</artifactId>
    <version>2.10.1</version>
</dependency>    

03版本代码HSSF

//03版本导出
@Test
public void testWrite03() throws Exception {

    String PATH="D:\\ideaProtect\\excel\\src\\main\\java\\com\\lim";

    //1、创建工作簿03版
    Workbook workbook=new HSSFWorkbook();
    //2、创建一个工作表
    Sheet sheet=workbook.createSheet("单选题");
    //3、创建一个行(1,1)
    Row row1=sheet.createRow(0);
    //3、创建一个单元格
    Cell cell11=row1.createCell(0);
    cell11.setCellValue("题目内容");
 ...

    //生成一张表(IO流)
    FileOutputStream fileOutputStream=new FileOutputStream(PATH+"试题导入模板.xls");

    workbook.write(fileOutputStream);

    //关闭流
    fileOutputStream.close();

    System.out.println("excel生成完毕");
}

07版本代码XSSF

@Test
public void testWrite07() throws Exception {

    String PATH="D:\\ideaProtect\\excel\\src\\main\\java\\com\\lim";

    //1、创建工作簿07版
    Workbook workbook=new XSSFWorkbook();
    //2、创建一个工作表
    Sheet sheet=workbook.createSheet("单选题");
    //3、创建一个行(1,1)
    Row row1=sheet.createRow(0);
    //3、创建一个单元格
    Cell cell11=row1.createCell(0);
    cell11.setCellValue("题目内容");

    //生成一张表(IO流)
    FileOutputStream fileOutputStream=new FileOutputStream(PATH+"试题导入模板.xlsx");

    workbook.write(fileOutputStream);

    //关闭流
    fileOutputStream.close();

    System.out.println("excel生成完毕");
}

03和07的区别

工作簿对象不同,生成文件后缀不同。

数据批量导入

大文件写入HSSF

缺点:最多只能处理65536行,否则会抛出异常

优点:过程中写入缓存,不操作磁盘,最后一次性写入磁盘,速度快

  //开始时间
    long begin=System.currentTimeMillis();

    //1、创建一个工作簿
    Workbook workbook=new HSSFWorkbook();
    //2、创建一个工作表
    Sheet sheet=workbook.createSheet();

    for (int i = 0; i < 65536; i++) {
        //3、创建行
        Row row1=sheet.createRow(i);
        for (int j = 0; j < 10; j++) {
            //4、创建单元格
            Cell cell11=row1.createCell(j);
            cell11.setCellValue(j);
        }
    }

    //结束时间
    long end=System.currentTimeMillis();

    System.out.println("over");

    FileOutputStream fileOutputStream=new FileOutputStream(PATH+"testWrite03BigData.xls");

    workbook.write(fileOutputStream);
    fileOutputStream.close();
    double time=(double)end-begin;
    System.out.println("time = " + time);
大文件写入XSSF

缺点:写数据是速度非常慢,非常耗内存,也会发生内存溢出,如100万条

有点:可以写入较大的数据量,如20万条

  //开始时间
        long begin=System.currentTimeMillis();

        //1、创建一个工作簿
        Workbook workbook=new XSSFWorkbook();
        //2、创建一个工作表
        Sheet sheet=workbook.createSheet();

        for (int i = 0; i < 65536; i++) {
            //3、创建行
            Row row1=sheet.createRow(i);
            for (int j = 0; j < 10; j++) {
                //4、创建单元格
                Cell cell11=row1.createCell(j);
                cell11.setCellValue(j);
            }
        }

        //结束时间
        long end=System.currentTimeMillis();

        System.out.println("over");

        FileOutputStream fileOutputStream=new FileOutputStream(PATH+"testWrite07BigData.xlsx");

        workbook.write(fileOutputStream);
        fileOutputStream.close();
        double time=(double)end-begin;
        System.out.println("time = " + time);
    

相较于HSSF来说,慢了很多,

大文件写入SXSSF

XSSF的升级

优点:可以写非常大的数据量,如100万条甚至更多,写数据速度快占用更少内存

注意:

过程中会产生临时文件,需要清理临时文件

默认由100条记录被保存在内存中,如果超过这数量,则最前面的数据被写入临时文件

如果想自定义内存中数据的数量,可以使用new SXSSFWorkbook(数量)

 public void testWrite07BigDataS() throws IOException {
        //开始时间
        long begin=System.currentTimeMillis();

        //1、创建一个工作簿
        Workbook workbook=new SXSSFWorkbook();
        //2、创建一个工作表
        Sheet sheet=workbook.createSheet();

        for (int i = 0; i < 65536; i++) {
            //3、创建行
            Row row1=sheet.createRow(i);
            for (int j = 0; j < 10; j++) {
                //4、创建单元格
                Cell cell11=row1.createCell(j);
                cell11.setCellValue(j);
            }
        }

        //结束时间
        long end=System.currentTimeMillis();

        System.out.println("over");

        FileOutputStream fileOutputStream=new FileOutputStream(PATH+"testWrite07BigDataS.xlsx");

        workbook.write(fileOutputStream);
        fileOutputStream.close();
        //清理临时文件
        ((SXSSFWorkbook) workbook).dispose();
        double time=(double)end-begin;
        System.out.println("time = " + time);
    }

SXSSFWorkbook来自官方的解释:实现“BigGridDemo”策略的流式XSSFWorkbook版本,这允许写入非常大的文件而不会耗尽内存,因为任何时候只有可配置的行部分被保存在内存中。

请注意,仍然可能会消耗大量内存,这些内存基于您正在使用的功能,例如合并区域,注释......仍然只存储在内存中,因此如果广泛使用,可能需要大量内存。

再使用POI的时候内存问题Jprofile监控。

POI-Excel读

03版本代码HSSF

 public void testRead03() throws Exception {

        //获取文件流
        FileInputStream fileInputStream=new FileInputStream(PATH+"lim试题导入模板.xls");

        //1、创建一个工作簿,使Excel能操作的我们这边都能操作
        Workbook workbook=new HSSFWorkbook(fileInputStream);
        //2、获取一个工作表
        Sheet sheet=workbook.getSheetAt(0);
        //3、获取行
        Row row=sheet.getRow(0);
        //4、获取单元格
        Cell cell11=row.getCell(0);

        System.out.println(cell11.getStringCellValue());

        fileInputStream.close();
    }

07版本代码XSSF

   public void testRead07() throws Exception {

        //获取文件流
        FileInputStream fileInputStream=new FileInputStream(PATH+"lim试题导入模板.xlsx");

        //1、创建一个工作簿,使Excel能操作的我们这边都能操作
        Workbook workbook=new XSSFWorkbook(fileInputStream);
        //2、获取一个工作表
        Sheet sheet=workbook.getSheetAt(0);
        //3、获取行
        Row row=sheet.getRow(0);
        //4、获取单元格
        Cell cell11=row.getCell(0);

        System.out.println(cell11.getStringCellValue());

        fileInputStream.close();
    }

类型转换

  public void testCellType() throws Exception{
        //获取文件流
        FileInputStream fileInputStream=new FileInputStream(PATH+"lim试题导入模板.xlsx");
        Workbook workbook=new XSSFWorkbook(fileInputStream);
        Sheet sheet=workbook.getSheetAt(0);
        //获取标题内容,标题行
        Row rowTitle=sheet.getRow(0);
        if (rowTitle!=null){
            //获取列数量
            int cellsCount=rowTitle.getPhysicalNumberOfCells();
            for (int cellNum=0;cellNum<cellsCount;cellNum++){
                Cell cell=rowTitle.getCell(cellNum);
                if (cell!=null){
                    int cellType=cell.getCellType();
                    String cellValue=cell.getStringCellValue();
                    System.out.print(cellType+cellValue+"|");
                }
            }
            System.out.println();

        }

        //获取下面的数据
        int rowCount=sheet.getPhysicalNumberOfRows();
        for (int rowNum = 1; rowNum <rowCount ; rowNum++) {
            Row rowData=sheet.getRow(rowNum);
            if (rowData!=null){
                int cellsCount=rowTitle.getPhysicalNumberOfCells();
                for (int cellNum = 0; cellNum <cellsCount ; cellNum++) {
                    System.out.print("["+(rowNum+1)+","+(cellNum+1)+"]");
                    Cell cell=rowData.getCell(cellNum);
                    if (cell!=null){
                        int cellType=cell.getCellType();
                        String cellValue="";
                        switch (cellType){
                            case XSSFCell
                                    .CELL_TYPE_STRING://字符串
                                System.out.print("[String]");
                                cellValue=cell.getStringCellValue();
                                break;
                            case XSSFCell.CELL_TYPE_BOOLEAN://布尔
                                System.out.print("[boolean]");
                                cellValue=String.valueOf(cell.getBooleanCellValue());
                                break;
                            case XSSFCell.CELL_TYPE_BLANK://空
                                System.out.print("[blank]");
                                break;
                            case XSSFCell.CELL_TYPE_NUMERIC:
                                if (HSSFDateUtil.isCellDateFormatted(cell)){
                                    System.out.print("[日期]");
                                    Date date=cell.getDateCellValue();
                                    cellValue=new DateTime(date).toString("yyyy-MM-dd");
                                }else {
                                    System.out.print("[数字]");
                                    cell.setCellType(XSSFCell.CELL_TYPE_STRING);
                                    cellValue=cell.toString();
                                }
                                break;
                            case XSSFCell.CELL_TYPE_ERROR://布尔
                                System.out.print("[数据类型错误]");
                                cellValue=String.valueOf(cell.getBooleanCellValue());
                                break;
                        }
                        System.out.println(cellValue);
                    }
                }
                System.out.println();
            }

        }

        fileInputStream.close();

    }

POI方式小结

2、EasyExcel

依赖

<dependencies>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>2.0.0-beta2</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.62</version>
        </dependency>
    </dependencies>

写入

1、新建导入模板类

@Data
public class SingleChoiceQuestion {

    //忽略
    @ExcelIgnore
    private Integer quesId;

    @ExcelProperty("题目内容")
    private String quesContext;

    @ExcelProperty("知识点")
    private String knowledge;
    
    ...

}
public class easyTest {
    String PATH="D:\\exam\\src\\main\\java\\com\\lim\\exam\\";
    private List<singleChoiceQuestion> questions(){
       List<singleChoiceQuestion> list =new ArrayList<singleChoiceQuestion>();
       singleChoiceQuestion singleChoiceQuestion =new singleChoiceQuestion();
       singleChoiceQuestion.setQuesContext("1+1=");
       singleChoiceQuestion.setKnowledge("数学");
      ...
       list.add(singleChoiceQuestion);
        return list;
    }

    //根据list写入
   // @org.junit.Test
    public void simpleWrite() {
        // 写法1 JDK8+
        // since: 3.0.0-beta1
        String fileName = PATH+"easyTest.xlsx";
        // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
        // 如果这里想使用03 则 传入excelType参数即可
        //方法一:单个sheet写入
        EasyExcel.write(fileName, Question.class).sheet(0,"单选").doWrite(questions());
        //方法二:多个sheet写入
        ExcelWriter excelWriter = null;
        try {
            excelWriter = EasyExcel.write(fileName, SingleChoiceQuestion.class).build();
            WriteSheet mainSheet = EasyExcel.writerSheet(0, "表1").head(SingleChoiceQuestion.class).build();
            excelWriter.write(questions(), mainSheet);
            WriteSheet mainSheet1= EasyExcel.writerSheet(1, "表2").head(SingleChoiceQuestion.class).build();
            excelWriter.write(questions(), mainSheet1);
        } finally {
            // 千万别忘记finish 会帮忙关闭流
            if (excelWriter != null) {
                excelWriter.finish();
            }
        }
        }
  }

写出(并插入到数据库)

1、新建一个读取data

@Getter
@Setter
@EqualsAndHashCode
public class JudgeQuestionData {
    
    private String quesContext;
    
    private String knowledge;
    
 ...
}

2、监听器

@Slf4j
public class DemoDataListener extends AnalysisEventListener<SingleChoiceQuestionData> {

    /**
     * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 100;
    /**
     * 缓存的数据
     */
    List<SingleChoiceQuestionData> list = new ArrayList<SingleChoiceQuestionData>();
    /**
     * 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
     */
    private QuestionDAO questionDAO;

    public DemoDataListener() {
        // 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数
        questionDAO = new QuestionDAO();
    }

    public List<SingleChoiceQuestionData> getDatas() {
        return list;
    }

    public void setDatas(List<SingleChoiceQuestionData> singleChoiceQuestionData) {
        this.list = singleChoiceQuestionData;
    }


    /**
     * 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
     *
     * @param questionDAO
     */
    public DemoDataListener(QuestionDAO questionDAO) {
        this.questionDAO = questionDAO;
    }

    /**
     * 这个每一条数据解析都会来调用
     *
     * @param data    one row value. Is is same as {@link AnalysisContext#readRowHolder()}
     * @param context
     */
    @Override
    public void invoke(SingleChoiceQuestionData data, AnalysisContext context) {
        System.out.println(JSON.toJSONString(data));
        list.add(data);
        // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
        if (list.size() >= BATCH_COUNT) {
            saveData();
            // 存储完成清理 list
            list.clear();
        }
    }

    /**
     * 所有数据解析完成了 都会来调用
     *
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 这里也要保存数据,确保最后遗留的数据也存储到数据库
        saveData();
        log.info("所有数据解析完成!");
    }

    /**
     * 加上存储数据库
     */
    private void saveData() {
        log.info("{}条数据,开始存储数据库!", list.size());
        System.out.println(list);
        //questionDAO.save(list);

        log.info("存储数据库成功!");
    }
   }

3、mapper层

@Repository
public interface QuestionMapper extends BaseMapper<Question> {
    @Insert("<script>" +
            "INSERT INTO Question" +
            "        (ques_context,knowledge)" +
            "        VALUES" +
            "        <foreach collection ='list' item='questions' separator =','>" +
            "            (#{questions.quesContext}, #{questions.knowledge})" +
            "        </foreach >" +
            "</script>")
    void insertQuestion(@Param("list") List<SingleChoiceQuestionData> questions);

}
   public void simpleRead(String path) {
        // 写法1:JDK8+ ,不用额外写一个DemoDataListener
        // since: 3.0.0-beta1
        String fileName =  path+"easyTest.xlsx";
        // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
        // 这里每次会读取3000条数据 然后返回过来 直接调用使用数据就行
        //EasyExcel.read(fileName, QuestionData.class, new DemoDataListener()).sheet().doRead();
        //监视器
        DemoDataListener listener = new DemoDataListener();
        ExcelReader excelReader = EasyExcel.read(fileName, listener).build();
        // 第一个sheet读取类型
        ReadSheet readSheet1 = EasyExcel.readSheet(0).head(QuestionData.class).build();

        // 第二个sheet读取类型
        ReadSheet readSheet2 = EasyExcel.readSheet(1).head(QuestionData.class).build();


        // 开始读取第一个sheet
        excelReader.read(readSheet1);
        //excel sheet0 信息
        List<QuestionData> list = listener.getDatas();
        System.out.println("questionMapper = " + questionMapper);
        System.out.println("userMapper = " + userMapper);
        //插入数据库
        questionMapper.insertQuestion(list);
        // 清空之前的数据
        listener.getDatas().clear();

        // 开始读取第二个sheet
        excelReader.read(readSheet2);
        //excel sheet1 信息
        List<QuestionData> entry = listener.getDatas();

    }
posted @   lim_sy  阅读(172)  评论(0编辑  收藏  举报
编辑推荐:
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示