JAVA使用hutool poi工具读取Excel表格指定行列范围的数据
1.pom.xml依赖配置
<!-- huTool工具箱 --> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.7.22</version> </dependency> <!-- poi --> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>5.1.0</version> </dependency>
2.代码
单元格数据模型对象
package com.hdwang.exceltest; import cn.hutool.json.JSONUtil; /** * 单元格数据 */ public class CellData { /** * 行号 */ private int rowIndex; /** * 列号 */ private int cellIndex; /** * 单元格数值 */ private Object value; public int getRowIndex() { return rowIndex; } public void setRowIndex(int rowIndex) { this.rowIndex = rowIndex; } public int getCellIndex() { return cellIndex; } public void setCellIndex(int cellIndex) { this.cellIndex = cellIndex; } public Object getValue() { return value; } public void setValue(Object value) { this.value = value; } @Override public String toString() { return JSONUtil.toJsonStr(this); } }
Bean对象属性赋值注解
package com.hdwang.exceltest; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 列号,用于给bean对象的属性赋指定列的值 */ @Retention(RetentionPolicy.RUNTIME) @Target(value = {ElementType.FIELD}) public @interface ColIndex { /** * 列索引号(从0开始),与name二者填一个即可,优先级高于name * * @return */ int index() default -1; /** * 列名称(从A开始),与index二者填一个即可,优先级低于index * * @return */ String name() default ""; }
表格数据对象
package com.hdwang.exceltest; import cn.hutool.json.JSONUtil; /** * 证券月报 */ public class ZhenquanReport { /** * 名称 */ @ColIndex(name = "B") private String name; /** * 数值 */ @ColIndex(index = 2) private String value; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } @Override public String toString() { return JSONUtil.toJsonStr(this); } }
读取方法代码
package com.hdwang.exceltest; import cn.hutool.poi.excel.ExcelReader; import cn.hutool.poi.excel.ExcelUtil; import cn.hutool.poi.excel.ExcelWriter; import cn.hutool.poi.excel.cell.CellHandler; import org.apache.commons.collections4.CollectionUtils; import org.apache.poi.ss.usermodel.*; import java.io.File; import java.lang.reflect.Field; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; public class Main { public static void main(String[] args) { try { File templateFile = new File("C:\\Users\\hdwang\\Desktop\\test.xlsx"); List<List<CellData>> rowDataList = readExcelData(templateFile, 2, Integer.MAX_VALUE, 1, Integer.MAX_VALUE); System.out.println(rowDataList); List<ZhenquanReport> reports = convertExcelDataToBeanList(rowDataList, ZhenquanReport.class); System.out.println(reports); } catch (Exception ex) { ex.printStackTrace(); } } /** * 读取表格数据 * * @param templateFile 文件 * @param startRowIndex 起始行号(从0开始) * @param endRowIndex 结束行号(从0开始) * @param startCellIndex 起始列号(从0开始) * @param endCellIndex 结束列号(从0开始) * @return 表格数据 */ private static List<List<CellData>> readExcelData(File templateFile, int startRowIndex, int endRowIndex, int startCellIndex, int endCellIndex) { ExcelReader excelReader = ExcelUtil.getReader(templateFile, 0); List<List<CellData>> rowDataList = new ArrayList<>(); AtomicInteger rowIndex = new AtomicInteger(-1); excelReader.read(startRowIndex, endRowIndex, new CellHandler() { @Override public void handle(Cell cell, Object value) { if (cell == null) { //无单元格跳过 return; } if (cell.getColumnIndex() < startCellIndex || cell.getColumnIndex() > endCellIndex) { //列号不在范围内跳过 return; } //新行的数据 if (cell.getRowIndex() != rowIndex.get()) { rowDataList.add(new ArrayList<>()); } rowIndex.set(cell.getRowIndex()); //取出新行数据对象存储单元格数据 List<CellData> cellDataList = rowDataList.get(rowDataList.size() - 1); CellData cellData = new CellData(); cellData.setRowIndex(cell.getRowIndex()); cellData.setCellIndex(cell.getColumnIndex()); cellData.setValue(value); cellDataList.add(cellData); } }); return rowDataList; } /** * 转换表格数据为bean对象列表 * * @param rowDataList 表格数据 * @param tClass bean类型 * @param <T> * @return bean对象列表 */ private static <T> List<T> convertExcelDataToBeanList(List<List<CellData>> rowDataList, Class<T> tClass) { if (CollectionUtils.isEmpty(rowDataList)) { return new ArrayList<>(); } List<T> beanList = new ArrayList<>(); for (List<CellData> rowData : rowDataList) { try { //实例化bean对象 T bean = tClass.newInstance(); //遍历字段并赋值 Field[] fields = tClass.getDeclaredFields(); for (Field field : fields) { if (field.isAnnotationPresent(ColIndex.class)) { ColIndex colIndex = field.getAnnotation(ColIndex.class); int index = colIndex.index(); String name = colIndex.name(); if (index != -1) { //do nothing } else if (!"".equals(name)) { //列名转索引号(补0为了适应下述方法) index = ExcelUtil.colNameToIndex(name + "0"); } else { throw new RuntimeException("请设置列号(ColIndex注解值必须配置一个)"); } //从行数据中找到指定单元格数据给字段赋值 final int i = index; CellData cellData = rowData.stream().filter(x -> x.getCellIndex() == i).findFirst().orElse(null); if (cellData != null) { Object value = cellData.getValue(); if (field.getType().getName().equals(String.class.getName())) { value = String.valueOf(value); } field.setAccessible(true); field.set(bean, value); } } } beanList.add(bean); } catch (Exception ex) { throw new RuntimeException("实例化对象失败", ex); } } return beanList; } }
3.表格
4.输出结果
表格数据对象
[ [ { "cellIndex": 1, "rowIndex": 2, "value": "净资产" }, { "cellIndex": 2, "rowIndex": 2, "value": 10000 }, { "cellIndex": 3, "rowIndex": 2, "value": " " }, { "cellIndex": 4, "rowIndex": 2, "value": 1 } ], [ { "cellIndex": 1, "rowIndex": 3, "value": "市值" }, { "cellIndex": 2, "rowIndex": 3, "value": 20000 }, { "cellIndex": 4, "rowIndex": 3, "value": 2 } ], [ { "cellIndex": 1, "rowIndex": 4, "value": "标题" } ], [ { "cellIndex": 1, "rowIndex": 5, "value": "净利润" }, { "cellIndex": 2, "rowIndex": 5, "value": 1000 }, { "cellIndex": 4, "rowIndex": 5, "value": 3 } ] ]
转成bean对象列表
[{"name":"净资产","value":"10000"}, {"name":"市值","value":"20000"}, {"name":"标题"}, {"name":"净利润","value":"1000"}]
5.原理总结
hutool poi 工具对POI进行了包装,实现了很多功能函数。可是在项目实践中发现还是有所不足。现在自己编写自定义取行列范围的数据并实现模型转换功能,从而方便对表格实现数据读取与单元格定位操作等。相关实现技术原理如下:
(1) 基于 public void read(int startRowIndex, int endRowIndex, CellHandler cellHandler) 函数实现行过滤,在CellHandler内部实现列过滤,这样便实现了行列过滤,且取出了行列信息,便于对单元格定位等。
(2) 将CellData转换为Bean对象的时候,采用反射技术读取bean对象字段的注解配置的列号信息,从而找到指定的CellData取值并赋值到字段上去。
6.附录
最新源码文件:https://github.com/hdwang123/exceltest