POI

  • apache 的访问 excel 工具
  • 03版本 用 HSSFWorkbook,07版本用 XSSFWorkbook,多数据写出时07版本可用 SXSSFWorkbook提升效率

写Excel


Static String PATH="D:\\" //定义excel文件存储路径
new Workbook()  创建工作簿
workBook.createSheet 创建工作表
sheet.createRow 创建行
row.createCell  创建列(单元格)
cell.setCellValue 为单元格赋值
new FileOutputStream(PATH+"xx.xls|xx.xlsx") 创建文件输出流
workBook.write(FileOutputStream)  在指定路径下写出excel文件
FileOutputStream.close() 关闭流


  • 多数据写出的时候,使用 SXSSFWorkbook 时会生成临时文件,在关闭流之后可以清除临时文件
    ((SXSSFWorkbook)Workbook).dispose()

读Excel


Static String PATH="D:\\" //定义excel文件存储路径
new FileInputStream(PATH+"xx.xls|xx.xlsx")  获取文件流
new WorkBook(FileInputStream)  创建工作簿
WorkBook.getSheetAt(0)得到第一张工作表
Sheet.getRow得到行
Row.getCell得到列
Cell.getStringCellValue得到值
FileInputStream.close()关闭流


  • 读取值的时候一定要注意类型 Cell.getTypeCellValue

读取不同类型的数据


FileInputStream inputStream = new FileInputStream(PATH + "xx.xls");//文件输入流
        Workbook hssfWorkbook = new HSSFWorkbook(inputStream);//获取工作簿
        Sheet sheet = hssfWorkbook.getSheetAt(0);//获取第一张表
        Row rowTitle = sheet.getRow(0);//获取标题行
        if (rowTitle != null) {
            int cellCount = rowTitle.getPhysicalNumberOfCells();
            for (int i = 0; i < cellCount; i++) {
                Cell cell = rowTitle.getCell(i);
                if(cell!=null){
                    String cellValue=cell.getStringCellValue();
                    System.out.print(cellValue);
                }
            }

        }
        int rowCount=sheet.getPhysicalNumberOfRows();
        for(int i=1;i<=rowCount;i++){
            Row rowData = sheet.getRow(i);
            if (rowData != null) {

                int cellCount=rowData.getPhysicalNumberOfCells();
                for(int j=0;j<=cellCount;j++){
                    Cell cell=rowData.getCell(i);
                    if(cell!=null){
                        CellType cellType =cell.getCellType();
                        String cellValue="";
                        switch (cellType){
                            case STRING:
                                System.out.println("[String]");
                                cellValue=cell.getStringCellValue();
                                break;
                            case NUMERIC:
                                System.out.println("numeric");
                                if(HSSFDateUtil.isCellDateFormatted(cell)){
                                    System.out.println("日期");
                                    Date date = cell.getDateCellValue();
                                    new DateTime(date).toString("yyyy-MM-dd");
                                }else{
                                    //不是日期格式,防止数字过长
                                    System.out.println("转换为字符串");
                                    cell.setCellType(CellType.STRING);
                                    cellValue=cell.toString();
                                }
                                break;
                        }
                        System.out.println(cellValue);
                    }
                }
            }
        }


读取Excel计算公式


FileInputStream inputStream = new FileInputStream(PATH + "xx.xls");//文件输入流
        Workbook workbook = new HSSFWorkbook(inputStream);//获取工作簿
        Sheet sheet = workbook.getSheetAt(0);//获取第一张表
        Row row = sheet.getRow(4);//获取公式所在行
        Cell cell = row.getCell(0);//获取公式所在列
        FormulaEvaluator evaluator = new HSSFFormulaEvaluator((HSSFWorkbook) workbook);//获取公式
        CellType cellType = cell.getCellType();
        switch (cellType){
            case FORMULA:
                String formula = cell.getCellFormula();
                System.out.println(formula);
                CellValue cellValue = evaluator.evaluate(cell);
                System.out.println(cellValue);
                break;
        }

    }



EasyExcel

写Excel

  • 写好模板类
@Data
public class DemoData {
    @ExcelProperty("字符串标题")
    private String string;
    @ExcelProperty("日期标题")
    private Date date;
    @ExcelProperty("数字标题")
    private Double doubleData;
    /**
     * 忽略这个字段
     */
    @ExcelIgnore
    private String ignore;
}


  • 创建测试数据
private List<DemoData> data() {
        List<DemoData> list = new ArrayList<DemoData>();
        for (int i = 0; i < 10; i++) {
            DemoData data = new DemoData();
            data.setString("字符串" + i);
            data.setDate(new Date());
            data.setDoubleData(0.56);
            list.add(data);
        }
        return list;
    }


  • 写测试
public static String PATH = "D:\\";
@Test
    public void writeTest() {
        String fileName = PATH + "easyTest.xlsx";
        // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
        // 如果这里想使用03 则 传入excelType参数即可
        //write(文件路径+文件名,格式类)
        //sheet(表名)
        //doWrite(数据),实际开发中应该是从前端传过来的数据
        EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());
    }


读Excel

  • 实体类已经创建,跳过该步骤
  • 写好监听器,主要步骤为继承AnalysisEventListener,然后重写 invoke方法
package com.bookstore.easyexcel;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;


// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
public class DemoDataListener extends AnalysisEventListener<DemoData> {
    private static final Logger LOGGER = (Logger)                          LoggerFactory.getLogger(DemoDataListener.class);
    /**
     * 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 5;
    private DemoDAO demoDAO;
    
    List<DemoData> list = new ArrayList<DemoData>();

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


    public DemoDataListener(DemoDAO demoDAO) {
        this.demoDAO = demoDAO;
    }

    @Override
    public void invoke(DemoData 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();
        LOGGER.info("所有数据解析完成!");
    }

    /**
     * 加上存储数据库
     */
    private void saveData() {
//        LOGGER.info("{}条数据,开始存储数据库!", list.size());
        demoDAO.save(list);
        LOGGER.info("存储数据库成功!");
    }
}


  • 创建数据持久化层(为了逻辑完整,未写具体方法)
package com.bookstore.easyexcel;

import java.util.List;

/**
 * 假设这个是你的DAO存储。当然还要这个类让spring管理,当然你不用需要存储,也不需要这个类。
 **/
public class DemoDAO {

    public void save(List<DemoData> list) {
        // 如果是mybatis,尽量别直接调用多次insert,自己写一个mapper里面新增一个方法batchInsert,所有数据一次性插入
    }
}


  • 读测试
@Test
    public void readTest(){
        // 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
        // 写法1:
        String fileName = PATH + "easyTest.xlsx";
        // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
        EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();

    }