Excel的操作

POI

  • 将用户信息导出为 Excel 表格 → 导出
  • 将 Excel 表格中的数据导入到网站的数据库 → 导入
  • 开发中经常会涉及到 Excel 的处理,我就在这里好好的介绍一下如何玩
  • 操作 Excel 目前比较流行的就是 Apache POI 和 Alibaba EasyExcel

Apache POI

  • 03版本的 Excel 最大支持行数:65535
  • 07版本的 Excel

IDEA 多个项目的创建

  • 首先创建一个空工程
  • 基于这个空工程创建新项目,存在这个空工程当中

首先创建一个空的工程

创建之后进入IDEA页面之前会出来如下图中的页面,意思是说叫你添加一个项目模块,然后呢你就可以点击+号添加模块,弹出来的页面就和我们之前的页面一样,然后创建就行了,不懂IDEA的可以去我主页看那个Java文章里面有个IDEA的文章,我这里不在介绍了

Excel POI导出

导入需要的依赖

<dependencies>
    <!--03-->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi</artifactId>
        <version>3.9</version>
    </dependency>

    <!--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>
</dependencies>

Excel 的导出需要弄明白几个概念

  • 工作簿(Workbook) → Excel文件
  • 工作表(Sheet) → Excel文件中左下角的 Sheet1,下方的两个一个行一个列我就不解释了
  • 行(Row)
  • 列(Cell)

先来看个03版本的,我就直接上Code了,都需要做 Excel 的导入导出了,对应创建测试类呀,包结构呀我就不在说了上Code

public class Demo {

    private static final String PATH = "导出的Excel存放地址";

    public static void main(String[] args) throws Exception {
        // 1.创建一个工作簿
        Workbook workbook = new HSSFWorkbook();

        // 2.创建一个工作表
        Sheet sheet = workbook.createSheet("IT6666");

        // 3.创建行
        Row row = sheet.createRow(0);

        // 4.创建列
        Cell cell = row.createCell(0);

        // 5.设置列的值
        cell.setCellValue(new DateTime().toString("yyyy-MM-dd HH:mm:ss"));

        // 6.IO流写出文件,03版本是以.xls结尾
        FileOutputStream fos = new FileOutputStream(PATH);

        // 7.输出Excel
        workbook.write(fos);

        // 8.释放流
        fos.close();

        System.out.println("Excel 导出完毕");
    }
}

07版本基本上改一个实现类和一个导出的文件后缀名为xlsx即可,下面我给出改动code

Workbook workbook = new XSSFWorkbook();

下面来看一下效率,和03的问题,03版本最大只可以写道65536,超过了则会报以下异常,改一下循环结构的循环次数就可以自己验证

java.lang.IllegalArgumentException: Invalid row number (65536) outside allowable range (0..65535)
public static void Excel0307two() throws Exception {
    long beginTime = System.currentTimeMillis();

    // 1.创建一个工作簿
    Workbook workbook = new HSSFWorkbook();

    // 2.创建一个工作表
    Sheet sheet = workbook.createSheet();

    // 3.写入数据
    for (int rowNum = 0; rowNum < 65536; rowNum++) {
        Row row = sheet.createRow(rowNum);
        for (int cellNum = 0; cellNum < 10; cellNum++) {
            Cell cell = row.createCell(cellNum);
            cell.setCellValue(cellNum);
        }
    }

    FileOutputStream fos = new FileOutputStream(PATH);
    workbook.write(fos);
    System.out.println("数据导出完毕");

    // 4.释放资源
    fos.close();
    long endTime = System.currentTimeMillis();

    System.out.println("耗时:" + (double) (endTime - beginTime) / 1000);
}

07版本的话,可以写出65536,或者可以更大的数据量,07版本的话只需要改一下导出文件的后缀为xlsx和实现类即可,自己测试一下导出耗费的时间真的久

Workbook workbook = new XSSFWorkbook();

那么如何优化呢,只需要改一下实现类和清除临时文件即可

public static void Excel0307two() throws Exception {
	...

    // 1.创建一个工作簿
    Workbook workbook = new SXSSFWorkbook();

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

	...
}

Excel POI导入

03版本的导入

public static void Excel0307Import() throws Exception {
    // 1.获取文件流
    FileInputStream fis = new FileInputStream(PATH);

    // 2.创建一个工作簿
    Workbook workbook = new HSSFWorkbook(fis);

    // 3.得到表
    Sheet sheet = workbook.getSheetAt(0);

    // 4.得到行
    Row row = sheet.getRow(0);

    // 5.得到列
    Cell cell = row.getCell(0);

    System.out.println(cell.getNumericCellValue());

    // 6.释放流
    fis.close();
}

07版本的改一下实现类即可

public static void Excel0307Import() throws Exception {
	...

    // 2.创建一个工作簿
    Workbook workbook = new XSSFWorkbook(fis);

    ...
}

上面的导入只是单纯的获取一个单元格的数据下面我给出的是多数据的导入和读取

public static void Excel0307Import() throws Exception {
    // 1.获取文件流
    FileInputStream fis = new FileInputStream(PATH);

    // 2.创建一个工作簿
    Workbook workbook = new XSSFWorkbook(fis);

    // 3.获取一个工作表
    Sheet sheet = workbook.getSheetAt(0);

    // 4.获取表格的第一行也就是标题
    Row rowTitle = sheet.getRow(0);
    if (null != rowTitle) {
        // 获取列的总个数
        int cellCount = rowTitle.getPhysicalNumberOfCells();
        for (int cellNum = 0; cellNum < cellCount; cellNum++) {
            Cell cell = rowTitle.getCell(cellNum);
            System.out.print(cell.getStringCellValue() + " | ");
        }
        System.out.println();
    }

    // 获取行的总个数
    int rowCount = sheet.getPhysicalNumberOfRows();
    for (int rowNum = 1; rowNum < rowCount; rowNum++) {
        // 获取行
        Row rowData = sheet.getRow(rowNum);
        if (null != rowData) {
            // 获取单元格,获取当前这一行的所有列的个数
            int currentRowCells = rowData.getPhysicalNumberOfCells();
            for (int cellNum = 0; cellNum < currentRowCells; cellNum++) {
                Cell cell = rowData.getCell(cellNum);

                // 匹配当前单元格的数据类型
                if (null != cell) {
                    int cellType = cell.getCellType();
                    String cellValue = "";

                    switch (cellType) {
                        case HSSFCell.CELL_TYPE_STRING:
                            cellValue = cell.getStringCellValue();
                            break;
                        case HSSFCell.CELL_TYPE_BOOLEAN:
                            cellValue = String.valueOf(cell.getBooleanCellValue());
                            break;
                        case HSSFCell.CELL_TYPE_BLANK:
                            break;
                        case HSSFCell.CELL_TYPE_NUMERIC:
                            // 如果是日期类型
                            if (HSSFDateUtil.isCellDateFormatted(cell)) {
                                Date date = cell.getDateCellValue();
                                cellValue = new DateTime(date).toString("yyyy-MM-dd");
                            } else {
                                // 不是日期数据类型,那么就是数字为了防止数字过长可以以字符串表示
                                cell.setCellType(HSSFCell.CELL_TYPE_STRING);
                                cellValue = cell.toString();
                            }
                            break;
                        case HSSFCell.CELL_TYPE_ERROR:
                            break;
                        default:
                            break;
                    }
                    System.out.print(cellValue + " | ");
                }
            }
        }
    }
}

导入 Excel 单元格有公式,接下来我来介绍一下如何玩

public static void excel0307ImportFormula() throws Exception {
    // 1.获取文件流
    FileInputStream fis = new FileInputStream(PATH);

    // 2.创建一个工作簿
    Workbook workbook = new XSSFWorkbook(fis);

    // 3.获取一个工作表
    Sheet sheet = workbook.getSheetAt(0);

    // 4.获取有公式的哪一行
    Row row = sheet.getRow(4);

    // 5.获取列
    Cell cell = row.getCell(0);

    // 6.拿到计算公式 eval
    FormulaEvaluator formulaEvaluator = new HSSFFormulaEvaluator((HSSFWorkbook) workbook);

    // 7.输出单元格的内容
    int cellType = cell.getCellType();

    switch (cellType) {
        // 公式
        case Cell.CELL_TYPE_FORMULA:
            String formula = cell.getCellFormula();
            System.out.println(formula);

            // 计算
            CellValue evaluate = formulaEvaluator.evaluate(cell);
            String cellValue = evaluate.formatAsString();
            System.out.println(cellValue);
            break;
        default:
            break;
    }
}

EasyExcel

  • EasyExcel 是一个基于 Java 的简单、省内存的读写 Excel 的开源项目。在尽可能节约内存的情况下支持读写百 M 的 Excel

导出 Excel

引入所需依赖

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

    <!--小辣椒-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.12</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>
</dependencies>

添加实体类

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

导出的数据生成,通用的,后面的例子会使用到

public class Demo {
    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;
    }
}

最简单的导出

private static final String PATH = "D:/Devlop/IDEAProject/ExcelPoi-03-07/IT6666.xlsx";
@Test
public void simpleWrite() {
    String fileName = PATH;
    // 这里需要指定用哪个class去写,然后写到第一个sheet,名字为模板,然后文件流会自动关闭
    EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());
}

根据参数只导出指定列

不使用注解指定导出的列

private static final String PATH = "D:/Devlop/IDEAProject/ExcelPoi-03-07/IT6666.xlsx";
@Test
public void excludeOrIncludeWrite() {
    String fileName = PATH;

    // 根据用户传入字段,假设我们要忽略 date
    Set<String> excludeColumnFiledNames = new HashSet<String>();
    excludeColumnFiledNames.add("date");
    // 这里需要指定用哪个class去写,然后写到第一个sheet,名字为模板,然后文件流会自动关闭
    EasyExcel.write(fileName, DemoData.class).excludeColumnFiledNames(excludeColumnFiledNames).sheet("模板").doWrite(data());
}

指定导出的列

添加实体类,使用注解指定导出的列

@Data
public class IndexData {
    @ExcelProperty(value = "字符串标题", index = 0)
    private String string;
    @ExcelProperty(value = "日期标题", index = 1)
    private Date date;
    /**
     * 这里设置3, 会导致第二列是空的
     */
    @ExcelProperty(value = "数字标题", index = 3)
    private Double doubleData;
}
private static final String PATH = "D:/Devlop/IDEAProject/ExcelPoi-03-07/IT6666.xlsx";
@Test
public void indexWrite() {
    String fileName = PATH;
    // 这里需要指定用哪个class去写,然后写到第一个sheet,名字为模板,然后文件流会自动关闭
    EasyExcel.write(fileName, IndexData.class).sheet("模板").doWrite(data());
}

复杂表头导出

添加实体类,使用注解指定复杂的表头

@Data
public class ComplexHeadData {
    @ExcelProperty({"主标题", "字符串标题"})
    private String string;
    @ExcelProperty({"主标题", "日期标题"})
    private Date date;
    @ExcelProperty({"主标题", "数字标题"})
    private Double doubleData;
}
private static final String PATH = "D:/Devlop/IDEAProject/ExcelPoi-03-07/IT6666.xlsx";
@Test
public void complexHeadWrite() {
    String fileName = PATH;
    // 这里需要指定用哪个class去写,然后写到第一个sheet,名字为模板,然后文件流会自动关闭
    EasyExcel.write(fileName, ComplexHeadData.class).sheet("模板").doWrite(data());
}

重复多次导出(导出单个或多个Sheet)

private static final String PATH = "D:/Devlop/IDEAProject/ExcelPoi-03-07/IT6666.xlsx";

@Test
public void repeatedWrite() {
    String fileName = PATH;
    ExcelWriter excelWriter = null;
    try {
        // 这里,需要指定用哪个class去写
        excelWriter = EasyExcel.write(fileName, DemoData.class).build();
        // 这里注意,如果同一个sheet只要创建一次
        WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
        // 去调用导出,这里我调用了五次,实际使用时根据数据库分页的总页数来
        for (int i = 0; i < 5; i++) {
            // 分页去数据库查询数据,这里可以去数据库查询每一页的数据
            List<DemoData> data = data();
            excelWriter.write(data, writeSheet);
        }
    } finally {
        // 千万别忘记finish,会帮忙关闭流
        if (excelWriter != null) {
            excelWriter.finish();
        }
    }
}

日期、数字或者自定义格式转换

/**
 * @author BNTang
 */
public class CustomStringStringConverter implements Converter<String> {
    @Override
    public Class supportJavaTypeKey() {
        return String.class;
    }

    @Override
    public CellDataTypeEnum supportExcelTypeKey() {
        return CellDataTypeEnum.STRING;
    }

    /**
     * 这里读的时候会调用
     *
     * @param cellData            NotNull
     * @param contentProperty     Nullable
     * @param globalConfiguration NotNull
     * @return String
     */
    @Override
    public String convertToJavaData(CellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
        return "自定义:" + cellData.getStringValue();
    }

    /**
     * 这里是写的时候会调用 不用管
     *
     * @param value               NotNull
     * @param contentProperty     Nullable
     * @param globalConfiguration NotNull
     * @return CellData
     */
    @Override
    public CellData convertToExcelData(String value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
        return new CellData("自定义:" + value);
    }
}
@Data
public class ConverterData {
    /**
     * 我想所有的 字符串起前面加上"自定义:"三个字
     */
    @ExcelProperty(value = "字符串标题", converter = CustomStringStringConverter.class)
    private String string;
    /**
     * 我想写到excel 用年月日的格式
     */
    @DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")
    @ExcelProperty("日期标题")
    private Date date;
    /**
     * 我想写到excel 用百分比表示
     */
    @NumberFormat("#.##%")
    @ExcelProperty(value = "数字标题")
    private Double doubleData;
}
private static final String PATH = "D:/Devlop/IDEAProject/ExcelPoi-03-07/IT6666.xlsx";

@Test
public void converterWrite() {
    String fileName = PATH;
    // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
    EasyExcel.write(fileName, ConverterData.class).sheet("模板").doWrite(data());
}

EasyExcel导入

最简单的读

@Data
public class DemoData {
    private String string;
    private Date date;
    private Double doubleData;
}
// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new, 然后里面用到spring可以构造方法传进去

/**
 * @author BNTang
 */
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>();

    /**
     * 这个每一条数据解析都会来调用
     *
     * @param data    数据
     * @param context 上下文
     */
    @Override
    public void invoke(DemoData data, AnalysisContext context) {
        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());
        System.out.println(list);
        LOGGER.info("存储数据库成功!");
    }
}
private static final String PATH = "D:/Devlop/IDEAProject/ExcelPoi-03-07/IT6666Excel.xlsx";

@Test
public void simpleRead() {
    String fileName = PATH;
    // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
    EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();
}

指定列的下标或者列名

@Data
public class IndexOrNameData {
    /**
     * 强制读取第三个 这里不建议 index 和 name 同时用,要么一个对象只用index,要么一个对象只用name去匹配
     */
    @ExcelProperty(index = 2)
    private Double doubleData;
    /**
     * 用名字去匹配,这里需要注意,如果名字重复,会导致只有一个字段读取到数据
     */
    @ExcelProperty("字符串标题")
    private String string;
    @ExcelProperty("日期标题")
    private Date date;
}

之前的监听器的泛型类型改改即可

private static final String PATH = "D:/Devlop/IDEAProject/ExcelPoi-03-07/IT6666Excel.xlsx";

@Test
public void indexOrNameRead() {
    String fileName = PATH;
    // 这里默认读取第一个sheet
    EasyExcel.read(fileName, IndexOrNameData.class, new IndexOrNameDataListener()).sheet().doRead();
}

读多个sheet

private static final String PATH = "D:/Devlop/IDEAProject/ExcelPoi-03-07/IT6666Excel.xlsx";

@Test
public void repeatedRead() {
    String fileName = PATH;
    // 读取全部sheet
    // 这里需要注意 DemoDataListener的doAfterAllAnalysed 会在每个sheet读取完毕后调用一次。然后所有sheet都会往同一个DemoDataListener里面写
    EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).doReadAll();

    // 读取部分sheet
    fileName = PATH;
    ExcelReader excelReader = null;
    try {
        excelReader = EasyExcel.read(fileName).build();

        // 这里为了简单 所以注册了 同样的head 和Listener 自己使用功能必须不同的Listener
        ReadSheet readSheet1 =
                EasyExcel.readSheet(0).head(DemoData.class).registerReadListener(new DemoDataListener()).build();
        // 这里注意 一定要把sheet1 sheet2 一起传进去,不然有个问题就是03版的excel 会读取多次,浪费性能
        excelReader.read(readSheet1);
    } finally {
        if (excelReader != null) {
            // 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的
            excelReader.finish();
        }
    }
}

日期、数字或者自定义格式转换

@Data
public class ConverterData {
    /**
     * 我自定义 转换器,不管数据库传过来什么 。我给他加上“自定义:”
     */
    @ExcelProperty(converter = CustomStringStringConverter.class)
    private String string;
    /**
     * 这里用string 去接日期才能格式化。我想接收年月日格式
     */
    @DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")
    private String date;
    /**
     * 我想接收百分比的数字
     */
    @NumberFormat("#.##%")
    private String doubleData;
}
public class CustomStringStringConverter implements Converter<String> {
    @Override
    public Class supportJavaTypeKey() {
        return String.class;
    }

    @Override
    public CellDataTypeEnum supportExcelTypeKey() {
        return CellDataTypeEnum.STRING;
    }

    /**
     * 这里读的时候会调用
     *
     * @param cellData            NotNull
     * @param contentProperty     Nullable
     * @param globalConfiguration NotNull
     * @return String
     */
    @Override
    public String convertToJavaData(CellData cellData, ExcelContentProperty contentProperty,
                                    GlobalConfiguration globalConfiguration) {
        return "自定义:" + cellData.getStringValue();
    }

    /**
     * 这里是写的时候会调用 不用管
     *
     * @param value               NotNull
     * @param contentProperty     Nullable
     * @param globalConfiguration NotNull
     * @return CellData
     */
    @Override
    public CellData convertToExcelData(String value, ExcelContentProperty contentProperty,
                                       GlobalConfiguration globalConfiguration) {
        return new CellData(value);
    }
}
/**
 * @author BNTang
 */
public class ConverterDataListener extends AnalysisEventListener<ConverterData> {
    private static final Logger LOGGER = LoggerFactory.getLogger(ConverterDataListener.class);
    /**
     * 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 5;
    List<ConverterData> list = new ArrayList<ConverterData>();

    /**
     * 这个每一条数据解析都会来调用
     *
     * @param data    数据
     * @param context 上下文
     */
    @Override
    public void invoke(ConverterData data, AnalysisContext context) {
        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());
        System.out.println(list);
        LOGGER.info("存储数据库成功!");
    }
}
private static final String PATH = "D:/Devlop/IDEAProject/ExcelPoi-03-07/IT6666Excel.xlsx";

@Test
public void converterRead() {
    String fileName = PATH;
    // 这里 需要指定读用哪个class去读,然后读取第一个sheet
    EasyExcel.read(fileName, ConverterData.class, new ConverterDataListener())
            // 这里注意 我们也可以registerConverter来指定自定义转换器, 但是这个转换变成全局了, 所有java为string,excel为string的都会用这个转换器。
            // 如果就想单个字段使用请使用@ExcelProperty 指定converter
            // .registerConverter(new CustomStringStringConverter())
            // 读取sheet
            .sheet().doRead();
}

多行头

@Data
public class DemoData {
    private String string;
    private Date date;
    private Double doubleData;
}
/**
 * @author BNTang
 */
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>();

    /**
     * 这个每一条数据解析都会来调用
     *
     * @param data    数据
     * @param context 上下文
     */
    @Override
    public void invoke(DemoData data, AnalysisContext context) {
        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());
        System.out.println(list);
        LOGGER.info("存储数据库成功!");
    }
}
private static final String PATH = "D:/Devlop/IDEAProject/ExcelPoi-03-07/IT6666Excel.xlsx";

@Test
public void complexHeaderRead() {
    String fileName = PATH;
    // 这里 需要指定读用哪个class去读,然后读取第一个sheet
    EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet()
            // 这里可以设置1,因为头就是一行。如果多行头,可以设置其他值。不传入也可以,因为默认会根据DemoData 来解析,他没有指定头,也就是默认1行
            .headRowNumber(1).doRead();
}

同步的返回

@Data
public class DemoData {
    private String string;
    private Date date;
    private Double doubleData;
}
private static final String PATH = "D:/Devlop/IDEAProject/ExcelPoi-03-07/IT6666Excel.xlsx";
private static final Logger LOGGER = LoggerFactory.getLogger(DemoDataListener.class);

/**
 * 同步的返回,不推荐使用,如果数据量大会把数据放到内存里面
 */
@Test
public void synchronousRead() {
    String fileName = PATH;
    // 这里 需要指定读用哪个class去读,然后读取第一个sheet 同步读取会自动finish
    List<DemoData> list = EasyExcel.read(fileName).head(DemoData.class).sheet().doReadSync();
    for (DemoData data : list) {
        LOGGER.info("读取到数据:{}", JSON.toJSONString(data));
    }
    // 这里 也可以不指定class,返回一个list,然后读取第一个sheet 同步读取会自动finish
    List<Map<Integer, String>> listMap = EasyExcel.read(fileName).sheet().doReadSync();
    for (Map<Integer, String> data : listMap) {
        // 返回每条数据的键值对 表示所在的列 和所在列的值
        LOGGER.info("读取到数据:{}", JSON.toJSONString(data));
    }
}

读取表头数据

@Data
public class DemoData {
    private String string;
    private Date date;
    private Double doubleData;
}
/**
 * @author BNTang
 */
public class DemoHeadDataListener extends AnalysisEventListener<DemoData> {
    
    ...

    /**
     * 这里会一行行的返回头
     *
     * @param headMap 头的映射
     * @param context 上下文
     */
    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        LOGGER.info("解析到一条头数据:{}", JSON.toJSONString(headMap));
    }
}
private static final String PATH = "D:/Devlop/IDEAProject/ExcelPoi-03-07/IT6666Excel.xlsx";

@Test
public void headerRead() {
    String fileName = PATH;
    // 这里需要指定读用哪个class去读,然后读取第一个sheet
    EasyExcel.read(fileName, DemoData.class, new DemoHeadDataListener()).sheet().doRead();
}
posted @   BNTang  阅读(289)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示