1 Apache POI

1.1 POI简介

1.2 基本结构

  • HSSF - 提供读写Microsoft Excel格式档案的功能。03版本的excel,即<font color="red">.xls</font>为后缀的文件
  • XSSF - 提供读写Microsoft Excel OOXML格式档案的功能。07版本的excel,即<font color="red">.xlsx</font>为后缀的文件
  • HWPF - 提供读写Microsoft Word格式档案的功能。
  • HSLF - 提供读写Microsoft PowerPoint格式档案的功能。
  • HDGF - 提供读写Microsoft Visio格式档案的功能。

1.3 POI操作Excel

1.3.0 导入依赖

<!--xls 03版本-->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>3.9</version>
</dependency>
<!--xlsx 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>
<!--单元测试-->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>

1.3.1 POI-写

  • Workbook有三个实现类

    • HSSFWorkbook:03版本
    • XSSFWorkbook:07版本
    • SXSSFWorkbook:07版本加强,流处理,会产生临时文件,提高速度

1.3.1.1 03版本xls

    /**
     * 03 和 07 版本excel的写入代码基本相同
     * 不同:
     *  WorkBook实现类
     *      03 HSSFWorkbook
     *      07 XSSFWorkbook
     *  文件名后缀
     *      03 .xls
     *      07 .xlsx
     */

//HSSFWorkbook
@Test
public void testWrite03() throws IOException {
    //1. 创建一个工作簿 03版本是HSSFWorkbook
    Workbook workbook = new HSSFWorkbook();
    //2. 创建一个工作表
    Sheet sheet = workbook.createSheet("测试表格sheet1");

    //3. 创建一个行 横坐标为1
    Row row1 = sheet.createRow(0);

    //4. 创建一个单元格 单元格的纵坐标为1
    //(1,1)
    Cell cell11 = row1.createCell(0);
    cell11.setCellValue("03xls写入测试");
    //(1,2)
    Cell cell12 = row1.createCell(1);
    cell12.setCellValue(666);

    //(2,1)
    Row row2 = sheet.createRow(1);
    Cell cell21 = row2.createCell(0);
    cell21.setCellValue("21");
    //(2,2)
    Cell cell22 = row2.createCell(1);
    String dateTime = new DateTime().toString("yyyy-MM-ss");
    cell22.setCellValue(dateTime);

    //5. 生成表格 IO流
    FileOutputStream fileOutputStream = new FileOutputStream(path + "03xls写入测试.xls");
    workbook.write(fileOutputStream);

    //6. 关闭流
    fileOutputStream.close();

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

1.3.1.2 07版本xlsx

//XSSFWorkbook
@Test
public void testWrite07() throws IOException {
    //1. 创建一个工作簿 07版本是HSSFWorkbook
    Workbook workbook = new XSSFWorkbook();
    //2. 创建一个工作表
    Sheet sheet = workbook.createSheet("07测试表格sheet1");

    //3. 创建一个行 横坐标为1
    Row row1 = sheet.createRow(0);

    //4. 创建一个单元格 单元格的纵坐标为1
    //(1,1)
    Cell cell11 = row1.createCell(0);
    cell11.setCellValue("07xlsx写入测试");
    //(1,2)
    Cell cell12 = row1.createCell(1);
    cell12.setCellValue(666);

    //(2,1)
    Row row2 = sheet.createRow(1);
    Cell cell21 = row2.createCell(0);
    cell21.setCellValue("21");
    //(2,2)
    Cell cell22 = row2.createCell(1);
    String dateTime = new DateTime().toString("yyyy-MM-ss");
    cell22.setCellValue(dateTime);

    //5. 生成表格 IO流
    FileOutputStream fileOutputStream = new FileOutputStream(path + "07xlsx写入测试.xlsx");
    workbook.write(fileOutputStream);

    //6. 关闭流
    fileOutputStream.close();

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

1.3.1.3 03版本写入大文件(上限65536)

//HSSF 03版本写入大文件
@Test
public void testWrite03BigData() throws IOException {
    //开始时间
    long begin = System.currentTimeMillis();

    //创建工作簿
    Workbook workbook = new HSSFWorkbook();
    //创建sheet
    Sheet sheet = workbook.createSheet();
    //插入数据
    for (int rowNum = 0; rowNum < 65536; rowNum++) {
        Row row = sheet.createRow(rowNum);
        for (int colNum = 0; colNum < 10; colNum++) {
            Cell cell = row.createCell(colNum);
            cell.setCellValue(colNum);
        }
    }

    System.out.println("over");
    //输出表格
    FileOutputStream fileOutputStream = new FileOutputStream(path + "03大文件.xls");
    workbook.write(fileOutputStream);
    //关闭流
    fileOutputStream.close();
    long end = System.currentTimeMillis();
    System.out.println((double)(end - begin)/1000);
}

1.3.14 07版本写入大文件

//07版本写入大文件XSSFWorkbook 耗时较长!优化,缓存
@Test
public void testWrite07BigData() throws IOException {
    //开始时间
    long begin = System.currentTimeMillis();

    //创建工作簿
    Workbook workbook = new XSSFWorkbook();
    //创建sheet
    Sheet sheet = workbook.createSheet();
    //插入数据
    for (int rowNum = 0; rowNum < 65536; rowNum++) {
        Row row = sheet.createRow(rowNum);
        for (int colNum = 0; colNum < 10; colNum++) {
            Cell cell = row.createCell(colNum);
            cell.setCellValue(colNum);
        }
    }

    System.out.println("over");
    //输出表格
    FileOutputStream fileOutputStream = new FileOutputStream(path + "07大文件.xlsx");
    workbook.write(fileOutputStream);
    //关闭流
    fileOutputStream.close();
    long end = System.currentTimeMillis();
    System.out.println((double)(end - begin)/1000);
}

//SXSSFWorkbook 会生成临时文件
@Test
public void testWrite07BigDataS() throws IOException {
    //开始时间
    long begin = System.currentTimeMillis();

    //创建工作簿
    Workbook workbook = new SXSSFWorkbook();
    //创建sheet
    Sheet sheet = workbook.createSheet();
    //插入数据
    for (int rowNum = 0; rowNum < 65536; rowNum++) {
        Row row = sheet.createRow(rowNum);
        for (int colNum = 0; colNum < 10; colNum++) {
            Cell cell = row.createCell(colNum);
            cell.setCellValue(colNum);
        }
    }

    System.out.println("over");
    //输出表格
    FileOutputStream fileOutputStream = new FileOutputStream(path + "07大文件.xlsx");
    workbook.write(fileOutputStream);
    //关闭流
    fileOutputStream.close();

    //清除临时文件
    ((SXSSFWorkbook)workbook).dispose();

    long end = System.currentTimeMillis();
    System.out.println((double)(end - begin)/1000);
}

1.3.2 POI-读

1.3.2.1 03版本xls

@Test
public void testRead03() throws IOException {
    //1 获得流
    FileInputStream fileInputStream = new FileInputStream("D:\\prac_project\\java_base\\excel_prac\\03xls写入测试.xls");
    //2 获得工作簿 可以操作excel
    Workbook workbook = new HSSFWorkbook(fileInputStream);
    //3 获得表 可以操作sheet
    Sheet sheet = workbook.getSheetAt(0);

    //4. 获得单元格内容
    Row row = sheet.getRow(0);
    Cell cell = row.getCell(0);
    //获得字符串类型 String
    String value = cell.getStringCellValue();
    Cell cell1 = row.getCell(1);
    //获得数字类型
    double numericCellValue = cell1.getNumericCellValue();
    System.out.println(value);
    System.out.println(numericCellValue);

    //5 关闭流
    fileInputStream.close();
}

1.3.2.2 07版本xlsx

@Test
public void testRead07() throws IOException {
    //1 获得流
    FileInputStream fileInputStream = new FileInputStream("D:\\prac_project\\java_base\\excel_prac\\07xlsx写入测试.xlsx");
    //2 获得工作簿 可以操作excel
    Workbook workbook = new XSSFWorkbook(fileInputStream);
    //3 获得表 可以操作sheet
    Sheet sheet = workbook.getSheetAt(0);

    //4. 获得单元格内容
    Row row = sheet.getRow(0);
    Cell cell = row.getCell(0);
    //获得字符串类型 String
    String value = cell.getStringCellValue();
    Cell cell1 = row.getCell(1);
    //获得数字类型
    double numericCellValue = cell1.getNumericCellValue();
    System.out.println(value);
    System.out.println(numericCellValue);

    //5 关闭流
    fileInputStream.close();
}

1.3.2.3 03读不同的格式

//不同格式
@Test
public void testCellType() throws IOException {
    FileInputStream fileInputStream = new FileInputStream("D:\\prac_project\\java_base\\excel_prac\\商品.xls");

    Workbook workbook = new HSSFWorkbook(fileInputStream);
    Sheet sheet = workbook.getSheetAt(0);

    //获得表头
    Row rowTitle = sheet.getRow(0);
    if (rowTitle != null) {

        //获得这一行有几个单元格
        int cellCount = rowTitle.getPhysicalNumberOfCells();
        for (int cellNum = 0; cellNum < cellCount ; cellNum++) {
            Cell cell = rowTitle.getCell(cellNum);
            if (cell != null) {
                int cellType = cell.getCellType();
                String stringCellValue = cell.getStringCellValue();
                System.out.print(stringCellValue + "|");
            }

        }
        System.out.println();
    }

    //获取表中的内容
    int rowCount = sheet.getPhysicalNumberOfRows();
    for (int rowNum = 1; rowNum < rowCount; rowNum++) {
        //获得除表头的数据行对象
        Row rowData = sheet.getRow(rowNum);
        if (rowData != null) {
            //读取列
            int cellCount = rowData.getPhysicalNumberOfCells();
            for (int cellNum = 0; cellNum < cellCount ; 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 HSSFCell.CELL_TYPE_NUMERIC: //0 数字 (日期,普通数字)
                            System.out.print("【NUMERIC】");
                            if (HSSFDateUtil.isCellDateFormatted(cell)) {
                                //是日期
                                System.out.print("【日期】");
                                Date date = cell.getDateCellValue();
                                String dateTime = new DateTime(date).toString();
                                cellValue = dateTime;
                            } else {
                                //转换为字符串后输出,防止数字过长
                                System.out.print("【转为数字输出】");
                                cell.setCellType(HSSFCell.CELL_TYPE_STRING);
                                cellValue = cell.toString();
                            }

                            break;
                        case HSSFCell.CELL_TYPE_STRING: //1 字符串
                            System.out.print("【STRING】");
                            cellValue = cell.getStringCellValue();
                            break;
                        case HSSFCell.CELL_TYPE_FORMULA: //2 公式
                            System.out.print("【FORMULA】");
                            break;
                        case HSSFCell.CELL_TYPE_BLANK:// 3 空
                            System.out.print("【BLANK】");
                            break;
                        case HSSFCell.CELL_TYPE_BOOLEAN://4 布尔
                            System.out.print("【BOOLEAN】");
                            cellValue = String.valueOf(cell.getBooleanCellValue());
                            break;
                        case HSSFCell.CELL_TYPE_ERROR://5 错误
                            System.out.print("【ERROR】");
                            break;
                    }
                    System.out.print(cellValue);
                }
                System.out.println();

            }
        }

    }
    fileInputStream.close();

}

1.3.2.4 03读公式

//公式
@Test
public void testFormula() throws IOException {
    FileInputStream fileInputStream = new FileInputStream("D:\\prac_project\\java_base\\excel_prac\\公式.xls");

    Workbook workbook = new HSSFWorkbook(fileInputStream);

    Sheet sheet = workbook.getSheetAt(0);

    Row row = sheet.getRow(4);
    Cell cell = row.getCell(0);

    //新建公式计算器 最好以接口形式 类似与js的eval
    FormulaEvaluator formulaEvaluator = new HSSFFormulaEvaluator((HSSFWorkbook) workbook);

    //输出单元格的公式 和 按照公式计算出来的值
    int cellType = cell.getCellType();
    switch (cellType){
        case HSSFCell.CELL_TYPE_FORMULA:
            //公式
            String cellFormula = cell.getCellFormula();
            System.out.println(cellFormula);

            //按公式计算出来的值
            CellValue evaluate = formulaEvaluator.evaluate(cell);
            String cellValue = evaluate.formatAsString();
            System.out.println(cellValue);
            break;
    }
}

2 EasyExcel

2.1 EasyExcel简介

2.2 EasyExcel操作excel

2.2.0 导入依赖

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>2.2.0-beta2</version>
</dependency>

2.2.1 最简单的写

  • 创建数据实体类
@Data
public class DemoData {
    @ExcelProperty("字符串标题")
    private String string;
    @ExcelProperty("日期标题")
    private Date date;
    @ExcelProperty("数字标题")
    private Double doubleData;
    /**
     * 忽略这个字段
     */
    @ExcelIgnore
    private String ignore;
}
  • 最简单的写
/**
     * 最简单的写
     * <p>1. 创建excel对应的实体对象 参照{@link DemoData}
     * <p>2. 直接写即可
     */
@Test
public void simpleWrite() {
    // 写法1
    String fileName = path + "easyexcel.xlsx";
    // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
    // 如果这里想使用03 则 传入excelType参数即可
    EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());

    // 写法2
    fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
    // 这里 需要指定写用哪个class去写
    ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build();
    WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
    excelWriter.write(data(), writeSheet);
    // 千万别忘记finish 会帮忙关闭流
    excelWriter.finish();
}

2.2.2 最简单的读

  • 创建持久层
/**
 * 假设这个是你的DAO存储。当然还要这个类让spring管理,当然你不用需要存储,也不需要这个类。
 **/
public class DemoDAO {
    public void save(List<DemoData> list) {
        // 如果是mybatis,尽量别直接调用多次insert,自己写一个mapper里面新增一个方法batchInsert,所有数据一次性插入
    }
}
  • 创建回调监听器
// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
public class DemoDataListener extends AnalysisEventListener<DemoData> {
    private static final Logger LOGGER = LoggerFactory.getLogger(DemoDataListener.class);
    /**
     * 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 5;
    List<DemoData> list = new ArrayList<DemoData>();
    /**
     * 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
     */
    private DemoDAO demoDAO;
    public DemoDataListener() {
        // 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数
        demoDAO = new DemoDAO();
    }
    /**
     * 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
     *
     * @param demoDAO
     */
    public DemoDataListener(DemoDAO demoDAO) {
        this.demoDAO = demoDAO;
    }
    /**
     * 这个每一条数据解析都会来调用
     *
     * @param data
     *            one row value. Is is same as {@link AnalysisContext#readRowHolder()}
     * @param context
     */
    @Override
    public void invoke(DemoData data, AnalysisContext context) {
        System.out.println("解析到一条数据:" + JSON.toJSONString(data));
        LOGGER.info("解析到一条数据:{}", 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("存储数据库成功!");
    }
}
  • 最简单的读
/**
     * 最简单的读
     * <p>1. 创建excel对应的实体对象 参照{@link DemoData}
     * <p>2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener}
     * <p>3. 直接读即可
     */
@Test
public void simpleRead() {
    // 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
    // 写法1:
    String fileName = path + "easyexcel.xlsx";
    // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
    EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();

    // 写法2:
    fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
    ExcelReader excelReader = EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).build();
    ReadSheet readSheet = EasyExcel.readSheet(0).build();
    excelReader.read(readSheet);
    // 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的
    excelReader.finish();
}
Last modification:April 23rd, 2020 at 02:23 pm