愿你如阳光,明媚不忧伤。
- 1. EasyExcel & 语雀
- 2. 快速感受
- 2.1 读Excel
- 2.2 写Excel
- 2.3 Web上传与下载
- 3. 详解读取Excel
- 3.1 简单读取Excel
- 3.2 指定列的下标或列名
- 3.3 读取多个sheet
- 3.4 日期,数字或自定义格式转换
- 3.5 多行头
- 3.6 读取表头数据
- 3.7 额外信息(批注,超链接,合并单元格信息)
- 3.8 读取公式和单元格类型
- 3.9 数据转换等异常处理
- 3.10 web中的读
1. EasyExcel & 语雀
EasyExcel 是阿里巴巴开源的一个excel处理框架,以使用简单、节省内存著称。该技术重写了poi对07版Excel的解析,使内存从百兆降低到KB级别,并且再大的excel不会出现内存溢出。解决了POI技术的耗内存问题,并且提供了较好的API使用。不需要大量的代码就可以实现excel的操作功能。在上层做了模型转换的封装,让使用者更加简单方便。
语雀(yuque)是支付宝内部孵化的一款文档与知识管理工具。语雀使用了“结构化知识库管理”,形式上类似书籍的目录。与其他产品可以随意建立文档不同,语雀上的每一篇文档必须属于某一个知识库,语雀希望通过这样的产品设计,来从源头上帮助用户建立起知识管理的意识,培养良好的知识管理习惯。
了解更多 → EasyExcel 语雀
了解更多 → EasyExcel github
2. 快速感受 2.1 读Excel
public void read() {
String fileName = "demo.xlsx";
// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
// 参数一:读取的excel文件路径
// 参数二:读取sheet的一行,将参数封装在DemoData实体类中
// 参数三:读取每一行的时候会执行DemoDataListener监听器
EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();
}
2.2 写Excel
public void simpleWrite() {
String fileName = "demo.xlsx";
// 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
// 如果这里想使用03则传入excelType参数即可
// 参数一:写入excel文件路径
// 参数二:写入的数据类型是DemoData
// data()方法是写入的数据,结果是List集合
EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());
}
2.3 Web上传与下载
@GetMapping("download")
public void download(HttpServletResponse response) throws IOException {
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
response.setHeader("Content-disposition", "attachment;filename=demo.xlsx");
EasyExcel.write(response.getOutputStream(), DownloadData.class).sheet("模板").doWrite(data());
}
@PostMapping("upload")
@ResponseBody
public String upload(MultipartFile file) throws IOException {
EasyExcel.read(file.getInputStream(), DemoData.class, new DemoDataListener()).sheet().doRead();
return "success";
}
3. 详解读取Excel
使用之前需要引入依赖,最新的稳定版本为 2.2.11
- pom.xml
3.1 简单读取Excelcom.alibaba easyexcel 2.2.11
- EasyExcelData.xlsx
- EasyExcelData.java
创建与Excel标题相对应的实体类
public class EasyExcelData {
private String stringData;
private Date dateData;
private Double doubleData;
// 省略get和set
}
- EasyExcelDataListener.java
监听器:有个很重要的点 EasyExcelDataListener 不能被spring管理,每次读取excel都要new,如果使用了spring,请使用有参构造方法,把要被管理的类传进去。
@Component public class EasyExcelDataListener extends AnalysisEventListener{ // 用来操作Excel的每一条数据 List list = new ArrayList (); public EasyExcelDataListener() { } // public EasyExcelDataListener(EasyExcelDataDao easyExcelDataDao) { // this.easyExcelDataDao = easyExcelDataDao; // } @Override public void invoke(EasyExcelData easyExcelData, AnalysisContext analysisContext) { System.out.println("解析到一条数据:" + JSON.toJSONString(easyExcelData)); list.add(easyExcelData); } @Override public void doAfterAllAnalysed(AnalysisContext analysisContext) { System.out.println("所有数据解析完成!" + JSON.toJSONString(list)); } public List getList() { return list; } }
- EasyExcelDataController.java
读取Excel,这里提供两种方式读取
@RestController
@RequestMapping("/easyExcel")
public class EasyExcelDataController extends baseController {
@Resource
EasyExcelDataListener easyExcelDataListener;
// 指定需要读的excel文件
String fileName = "C:/Users/ISCCF_A/Desktop/EasyExcelData.xlsx";
@RequestMapping("/read")
public JsonResult> simpleRead(int mode) {
log.info(startLog(Thread.currentThread().getStackTrace()[1].getMethodName()));
if (mode == 1) {
// 写法1(默认第一个Sheet):
// 这里需要指定用哪个class去读,读取第一个sheet文件流后会自动关闭
EasyExcel.read(fileName, EasyExcelData.class, easyExcelDataListener).sheet().doRead();
} else {
// 一个文件一个reader
ExcelReader excelReader = null;
// 写法2(指定读取Sheet):
try {
excelReader = EasyExcel.read(fileName, EasyExcelData.class, easyExcelDataListener).build();
// 构建一个sheet 这里可以指定名字或者No
ReadSheet readSheet = EasyExcel.readSheet(0).build();
// 读取一个sheet
excelReader.read(readSheet);
} finally {
if (excelReader != null) {
// 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的
excelReader.finish();
}
}
}
log.info(endLog(Thread.currentThread().getStackTrace()[1].getMethodName()));
return new JsonResult<>(easyExcelDataListener.getList(), "读取Excel数据成功!");
}
}
浏览器访问 http://localhost:8080/easyExcel/read?mode=2
3.2 指定列的下标或列名- EasyExcelDataIndexOrName.java
修改对应实体类
public class EasyExcelDataIndexOrName {
@ExcelProperty(index = 2)
private Double doubleData;
@ExcelProperty("字符串标题")
private String string;
@ExcelProperty("日期标题")
private Date date;
// 省略get和set
}
- EasyExcelDataReadController.java
监听器只是泛型变了,请参照上面的监听器,具体代码不再展示。
@RestController
@RequestMapping("/easyExcelRead")
public class EasyExcelDataReadController extends baseController {
@Resource
EasyExcelDataReadListener easyExcelDataReadListener;
// 指定需要读的excel文件
String fileName = "C:/Users/ISCCF_A/Desktop/EasyExcelData.xlsx";
@RequestMapping("/indexOrName")
public JsonResult> indexOrNameRead() {
log.info(startLog(Thread.currentThread().getStackTrace()[1].getMethodName()));
EasyExcel.read(fileName, EasyExcelDataIndexOrName.class, easyExcelDataReadListener).sheet().doRead();
log.info(endLog(Thread.currentThread().getStackTrace()[1].getMethodName()));
return new JsonResult<>(easyExcelDataReadListener.getList(), "读取Excel数据成功!(指定列的下标或列名)");
}
}
浏览器访问 http://localhost:8080/easyExcelRead/indexOrName
3.3 读取多个sheet- EasyExcelDataReadController.java
监听器只是泛型变了,请参照上面的监听器,具体代码不再展示。
@RestController
@RequestMapping("/easyExcelRead")
public class EasyExcelDataReadController extends baseController {
@Resource
EasyExcelDataReadListener easyExcelDataReadListener;
// 指定需要读的excel文件
String fileName = "C:/Users/ISCCF_A/Desktop/EasyExcelData.xlsx";
@RequestMapping("/multiSheets")
public JsonResult> multiSheets(String model) {
log.info(startLog(Thread.currentThread().getStackTrace()[1].getMethodName()));
if ("all".equals(model)) {
// 读取全部sheet
// 这里需要注意 EasyExcelDataReadListener的doAfterAllAnalysed 会在每个sheet读取完毕后调用一次。然后所有sheet都会往同一个EasyExcelDataReadListener里面写
EasyExcel.read(fileName, EasyExcelDataIndexOrName.class, easyExcelDataReadListener).doReadAll();
} else {
// 读取部分sheet
ExcelReader excelReader = null;
try {
excelReader = EasyExcel.read(fileName).build();
// 这里为了简单注册了同样的head和Listener
ReadSheet readSheet1 =
EasyExcel.readSheet(0).head(EasyExcelDataIndexOrName.class).registerReadListener(easyExcelDataReadListener).build();
ReadSheet readSheet2 =
EasyExcel.readSheet(1).head(EasyExcelDataIndexOrName.class).registerReadListener(easyExcelDataReadListener).build();
// 这里注意 一定要把sheet1 sheet2 一起传进去,不然有个问题就是03版的excel 会读取多次,浪费性能
excelReader.read(readSheet1, readSheet2);
} finally {
if (excelReader != null) {
// 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的
excelReader.finish();
}
}
}
log.info(endLog(Thread.currentThread().getStackTrace()[1].getMethodName()));
return new JsonResult<>(easyExcelDataReadListener.getList(), "读取Excel数据成功!(读多个sheet)");
}
}
浏览器访问 http://localhost:8080/easyExcelRead/multiSheets
3.4 日期,数字或自定义格式转换- EasyExcelDataConverter.java
创建对应实体类,需要指定自定格式转换器
public class EasyExcelDataConverter {
@ExcelProperty(converter = CustomStringStringConverter.class)
private String string;
@DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")
private String date;
@NumberFormat("#.##%")
private String doubleData;
// 省略get和set
}
- CustomStringStringConverter.java
自定义转换器,需要实现Converter接口
public class CustomStringStringConverter implements Converter{ @Override public Class supportJavaTypeKey() { return String.class; } @Override public CellDataTypeEnum supportExcelTypeKey() { return CellDataTypeEnum.STRING; } @Override public String convertToJavaData(CellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { return "自定义格式:" + cellData.getStringValue(); } @Override public CellData convertToExcelData(String value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { return new CellData(value); } }
- EasyExcelDataReadController.java
监听器只是泛型变了,请参照上面的监听器,具体代码不再展示。
@RestController
@RequestMapping("/easyExcelRead")
public class EasyExcelDataReadController extends baseController {
@Resource
EasyExcelDataReadListener easyExcelDataReadListener;
@Resource
EasyExcelDataConverterListener easyExcelDataConverterListener;
// 指定需要读的excel文件
String fileName = "C:/Users/ISCCF_A/Desktop/EasyExcelData.xlsx";
@RequestMapping("/converterRead")
public JsonResult> converterRead() {
log.info(startLog(Thread.currentThread().getStackTrace()[1].getMethodName()));
// 这里注意 我们也可以registerConverter来指定自定义转换器(.registerConverter(new CustomStringStringConverter())), 但是这个转换变成全局了, 所有java为string,excel为string的都会用这个转换器。
// 如果就想单个字段使用请使用@ExcelProperty 指定converter
EasyExcel.read(fileName, EasyExcelDataConverter.class, easyExcelDataConverterListener).sheet().doRead();
log.info(endLog(Thread.currentThread().getStackTrace()[1].getMethodName()));
return new JsonResult<>(easyExcelDataConverterListener.getList(), "读取Excel数据成功!(自定义格式转换)");
}
}
浏览器访问 http://localhost:8080/easyExcelRead/converterRead
3.5 多行头- EasyExcelDataReadController.java
监听器只是泛型变了,请参照上面的监听器,具体代码不再展示。
@RestController
@RequestMapping("/easyExcelRead")
public class EasyExcelDataReadController extends baseController {
@Resource
EasyExcelDataReadListener easyExcelDataReadListener;
@Resource
EasyExcelDataConverterListener easyExcelDataConverterListener;
// 指定需要读的excel文件
String fileName = "C:/Users/ISCCF_A/Desktop/EasyExcelData.xlsx";
@RequestMapping("/multiHeaders")
public JsonResult> multiHeaders() {
log.info(startLog(Thread.currentThread().getStackTrace()[1].getMethodName()));
// 这里可以设置1,因为头就是一行。如果多行头,可以设置其他值。不传入也可以,EasyExcelDataConverter 来解析,他没有指定头,也就是默认1行
EasyExcel.read(fileName, EasyExcelDataConverter.class, easyExcelDataConverterListener).sheet().headRowNumber(1).doRead();
log.info(endLog(Thread.currentThread().getStackTrace()[1].getMethodName()));
return new JsonResult<>(easyExcelDataConverterListener.getList(), "读取Excel数据成功!(多行头)");
}
}
浏览器访问 http://localhost:8080/easyExcelRead/multiHeaders
3.6 读取表头数据- EasyExcelDataReadController.java
监听器里面多了一个方法,只要重写invokeHeadMap方法即可。
@Override public void invokeHead(MapheadMap, AnalysisContext context) { System.out.println("解析到一条头数据:" + JSON.toJSONString(headMap)); }
- EasyExcelDataReadController.java
@RestController
@RequestMapping("/easyExcelRead")
public class EasyExcelDataReadController extends baseController {
@Resource
EasyExcelDataReadListener easyExcelDataReadListener;
@Resource
EasyExcelDataConverterListener easyExcelDataConverterListener;
// 指定需要读的excel文件
String fileName = "C:/Users/ISCCF_A/Desktop/EasyExcelData.xlsx";
@RequestMapping("/headerRead")
public JsonResult
浏览器访问 http://localhost:8080/easyExcelRead/headerRead
3.7 额外信息(批注,超链接,合并单元格信息)- EasyExcelData.xlsx
在Excel中添加好超链接,批注,合并单元格等内容。
- EasyExcelExtraData.java
创建对应实体类
public class EasyExcelExtraData {
private String column1;
private String column2;
// 省略get和set
}
- EasyExcelDataExtraListener.java
监听器:里面多了一个 extra 方法
@Component public class EasyExcelDataExtraListener extends AnalysisEventListener{ // 用来操作Excel的额外数据 List list = new ArrayList<>(); // 用来显示Excel的额外数据的详细信息 List listExtra = new ArrayList<>(); public EasyExcelDataExtraListener() { } // public EasyExcelDataListener(EasyExcelDataDao easyExcelDataDao) { // this.easyExcelDataDao = easyExcelDataDao; // } @Override public void invoke(EasyExcelExtraData easyExcelData, AnalysisContext analysisContext) { System.out.println("解析到一条数据:" + JSON.toJSONString(easyExcelData)); list.add(easyExcelData); } @Override public void doAfterAllAnalysed(AnalysisContext analysisContext) { System.out.println("所有数据解析完成!" + JSON.toJSONString(list)); } @Override public void extra(CellExtra extra, AnalysisContext context) { listExtra.add("读取到了一条额外信息:" + JSON.toJSONString(extra)); System.out.println("读取到了一条额外信息:" + JSON.toJSONString(extra)); switch (extra.getType()) { case COMMENT: listExtra.add("额外信息是批注,在rowIndex:" + extra.getRowIndex() + ",columnIndex:" + extra.getColumnIndex() + "内容是:" + extra.getText()); System.out.println("额外信息是批注,在rowIndex:" + extra.getRowIndex() + ",columnIndex:" + extra.getColumnIndex() + "内容是:" + extra.getText()); break; case HYPERlink: if ("Sheet1!A1".equals(extra.getText())) { listExtra.add("额外信息是超链接,在rowIndex:" + extra.getRowIndex() + ",columnIndex:" + extra.getColumnIndex() + "内容是:" + extra.getText()); System.out.println("额外信息是超链接,在rowIndex:" + extra.getRowIndex() + ",columnIndex:" + extra.getColumnIndex() + "内容是:" + extra.getText()); } else if ("Sheet2!A1:B2".equals(extra.getText())) { listExtra.add("额外信息是超链接,而且覆盖了一个区间,在firstRowIndex:" + extra.getFirstRowIndex() + ",firstColumnIndex:" + extra.getFirstColumnIndex() + ",lastRowIndex:" + extra.getLastRowIndex() + ",lastColumnIndex:" + extra.getLastColumnIndex() + ",内容是:" + extra.getText()); System.out.println( "额外信息是超链接,而且覆盖了一个区间,在firstRowIndex:" + extra.getFirstRowIndex() + ",firstColumnIndex:" + extra.getFirstColumnIndex() + ",lastRowIndex:" + extra.getLastRowIndex() + ",lastColumnIndex:" + extra.getLastColumnIndex() + ",内容是:" + extra.getText()); } else { listExtra.add("Unknown hyperlink!"); System.out.println("Unknown hyperlink!"); } break; case MERGE: listExtra.add("额外信息是合并单元格,而且覆盖了一个区间在firstRowIndex:" + extra.getFirstRowIndex() + ",firstColumnIndex:" + extra.getFirstColumnIndex() + ",lastRowIndex:" + extra.getLastRowIndex() + ",lastColumnIndex:" + extra.getLastColumnIndex()); System.out.println( "额外信息是合并单元格,而且覆盖了一个区间在firstRowIndex:" + extra.getFirstRowIndex() + ",firstColumnIndex:" + extra.getFirstColumnIndex() + ",lastRowIndex:" + extra.getLastRowIndex() + ",lastColumnIndex:" + extra.getLastColumnIndex()); break; default: } } public List getExtraInfo() { listExtra.add(JSON.toJSONString(list)); return listExtra; } }
- EasyExcelDataReadController.java
@RestController
@RequestMapping("/easyExcelRead")
public class EasyExcelDataReadController extends baseController {
@Resource
EasyExcelDataReadListener easyExcelDataReadListener;
@Resource
EasyExcelDataConverterListener easyExcelDataConverterListener;
@Resource
EasyExcelDataExtraListener easyExcelDataExtraListener;
// 指定需要读的excel文件
String fileName = "C:/Users/ISCCF_A/Desktop/EasyExcelData.xlsx";
@RequestMapping("/extraRead")
public JsonResult> extraRead() {
log.info(startLog(Thread.currentThread().getStackTrace()[1].getMethodName()));
EasyExcel.read(fileName, EasyExcelExtraData.class, easyExcelDataExtraListener)
// 需要读取批注 默认不读取
.extraRead(CellExtraTypeEnum.COMMENT)
// 需要读取超链接 默认不读取
.extraRead(CellExtraTypeEnum.HYPERlink)
// 需要读取合并单元格信息 默认不读取
.extraRead(CellExtraTypeEnum.MERGE).sheet().doRead();
log.info(endLog(Thread.currentThread().getStackTrace()[1].getMethodName()));
return new JsonResult<>(easyExcelDataExtraListener.getExtraInfo(), "读取Excel数据成功!(额外信息)");
}
}
浏览器访问 http://localhost:8080/easyExcelRead/extraRead
- EasyExcelData.xlsx
在Excel中添加好公式等内容。
- EasyExcelExtraData.java
创建对应实体类
public class EasyExcelCellData {
private CellData string;
private CellData date;
private CellData doubleData;
private CellData formulaValue;
// 省略get和set
}
- EasyExcelDataReadController.java
监听器只是泛型变了,请参照上面的监听器,具体代码不再展示。
@RestController
@RequestMapping("/easyExcelRead")
public class EasyExcelDataReadController extends baseController {
@Resource
EasyExcelCellDataListener easyExcelCellDataListener;
// 指定需要读的excel文件
String fileName = "C:/Users/ISCCF_A/Desktop/EasyExcelData.xlsx";
@RequestMapping("/cellDataRead")
public JsonResult> cellDataRead() {
log.info(startLog(Thread.currentThread().getStackTrace()[1].getMethodName()));
EasyExcel.read(fileName, EasyExcelCellData.class, easyExcelCellDataListener).sheet().doRead();
log.info(endLog(Thread.currentThread().getStackTrace()[1].getMethodName()));
return new JsonResult<>(easyExcelCellDataListener.getList(), "读取Excel数据成功!(读取公式和单元格类型)");
}
}
浏览器访问 http://localhost:8080/easyExcelRead/cellDataRead
3.9 数据转换等异常处理- EasyExcelExtraData.java
创建错误的实体类(数据类型不匹配)
public class ExceptionEasyExcelData {
// 第一列数据是字符串类型,但是指定了日期类型
private Date date;
// 省略get和set
}
- ExceptionEasyExcelDataListener.java
监听器:里面多了一个方法,只要重写onException方法即可
@Component public class ExceptionEasyExcelDataListener extends AnalysisEventListener{ // 用来操作Excel的每一条数据 List list = new ArrayList<>(); StringBuffer sb = new StringBuffer(); @Override public void onException(Exception exception, AnalysisContext context) { System.out.println("解析失败,但是继续解析下一行:" + exception.getMessage()); // 如果是某一个单元格的转换异常 能获取到具体行号 if (exception instanceof ExcelDataConvertException) { ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException) exception; System.err.println("第" + excelDataConvertException.getRowIndex() + "行,第" + excelDataConvertException.getColumnIndex() + "列解析异常"); sb.append("解析失败,但是继续解析下一行:").append(exception.getMessage()).append("第").append(excelDataConvertException.getRowIndex()).append("行,第").append(excelDataConvertException.getColumnIndex()).append("列解析异常"); } } }
- EasyExcelDataReadController.java
@RestController
@RequestMapping("/easyExcelRead")
public class EasyExcelDataReadController extends baseController {
@Resource
ExceptionEasyExcelDataListener exceptionEasyExcelDataListener;
// 指定需要读的excel文件
String fileName = "C:/Users/ISCCF_A/Desktop/EasyExcelData.xlsx";
@RequestMapping("/exceptionRead")
public JsonResult exceptionRead() {
log.info(startLog(Thread.currentThread().getStackTrace()[1].getMethodName()));
EasyExcel.read(fileName, ExceptionEasyExcelData.class, exceptionEasyExcelDataListener).sheet().doRead();
log.info(endLog(Thread.currentThread().getStackTrace()[1].getMethodName()));
return new JsonResult<>(exceptionEasyExcelDataListener.getExceptionInfo(), "读取Excel数据成功!(数据转换等异常处理)");
}
}
浏览器访问 http://localhost:8080/easyExcelRead/exceptionRead
3.10 web中的读- EasyExcelDataReadController.java
监听器只是泛型变了,请参照上面的监听器,具体代码不再展示。
@RestController
@RequestMapping("/easyExcelRead")
public class EasyExcelDataReadController extends baseController {
@Resource
EasyExcelDataMultipartFileListener easyExcelDataMultipartFileListener;
// 指定需要读的excel文件
String fileName = "C:/Users/ISCCF_A/Desktop/EasyExcelData.xlsx";
@PostMapping("/multipartFileRead")
@ResponseBody
public JsonResult> multipartFileRead(MultipartFile file) throws IOException {
log.info(startLog(Thread.currentThread().getStackTrace()[1].getMethodName()));
EasyExcel.read(file.getInputStream(), EasyExcelDataMultipartFile.class, easyExcelDataMultipartFileListener).sheet().doRead();
log.info(endLog(Thread.currentThread().getStackTrace()[1].getMethodName()));
return new JsonResult<>(easyExcelDataMultipartFileListener.getList(), "读取Excel数据成功!(web中的读)");
}



