POI和EasyExcel


POI和easyExcel

场景:

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

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

内存问题:

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

easyExcel=一条一条写

excel元素

1、工作簿

2、工作表

3、行

4、列

1、POI方式实现

POI-Excel写

POI依赖



    org.apache.poi
    poi
    3.9

    


    org.apache.poi
    poi-ooxml
    3.9

    


    joda-time
    joda-time
    2.10.1
    

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

POI方式小结

2、EasyExcel

依赖


        
            com.alibaba
            easyexcel
            2.0.0-beta2
        
        
            com.alibaba
            fastjson
            1.2.62
        
    

写入

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 questions(){
       List list =new ArrayList();
       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 {

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

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

    public List getDatas() {
        return list;
    }

    public void setDatas(List 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 {
    @Insert("")
    void insertQuestion(@Param("list") List 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 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 entry = listener.getDatas();

    }