目录
一、需求描述
二、具体操作Excel的常用方法
方法一: 使用jxl
方法二: POI
方法三:EasyExcel
三、总结
一、需求描述
前端有时候会传送 Excel 文件给后端(Java)去解析,那我们作为后端该如何实现对 Excel 文件的解析和数据读取呢?
使用Java对Excel文件进行读写,文件后缀可能为.xls, .xlsx
二、具体操作Excel的常用方法
方法一: 使用jxl
JXL: 开源库,支持.xls文件(EXCEL 2003格式)的读写
注意: JXL不支持.xlsx文件(EXCEL 2007格式)的读写
使用:
1. pom.xml文件引入
<!--读取excel文件-->
<!-- https://mvnrepository.com/artifact/net.sourceforge.jexcelapi/jxl -->
<dependency>
<groupId>net.sourceforge.jexcelapi</groupId>
<artifactId>jxl</artifactId>
<version>2.6.12</version>
</dependency>
2. jxl读文件
import jxl.Cell;
import jxl.Sheet;
import jxl.Workbook;
import jxl.write.Label;
import jxl.write.WritableSheet;
import jxl.write.WritableWorkbook;
import jxl.write.WriteException;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
//读文件
/**
* 返回上传的Excel表格的内容
* Jxl是一款常用的Java中操作Excel的API,但其只对xls有效,对2007版本以上的Excel(xlsx)很难处理。
*/
public static List<String[]> parseExcel(String filePath) throws Exception {
List<String[]> list = new ArrayList<>();
Workbook wb = Workbook.getWorkbook(new File(filePath));
Sheet sheet = wb.getSheets()[0];
int columns = sheet.getRow(0).length;
for (int i = 0; i < sheet.getRows(); i++) {
String[] line = new String[columns];
for (int j = 0; j < columns; j++) {
Cell cell = sheet.getCell(j, i);
String content = null;
if (cell != null) {
content = cell.getContents();
}
if (content != null && content.trim().length() == 0) {
content = "";
}
line[j] = content;
}
list.add(line);
}
return list;
}
// 写文件
public static void write2File(String filePath, List<GradeVo> gradeVoList) throws IOException, WriteException {
// 写入文件
File file = new File(filePath);
// 创建工作簿
WritableWorkbook workbook = Workbook.createWorkbook(file);
// 创建工作表
WritableSheet sheet = workbook.createSheet("Sheet1", 0);
AtomicInteger rowIndex = new AtomicInteger(1);
try {
//写表头
Label label0 = new Label(0, 0, "学号");
sheet.addCell(label0);
label0 = new Label(1, 0, "姓名");
sheet.addCell(label0);
label0 = new Label(2, 0, "班级");
sheet.addCell(label0);
label0 = new Label(3, 0, "每次作业分");
sheet.addCell(label0);
label0 = new Label(4, 0, "作业平均分");
sheet.addCell(label0);
label0 = new Label(5, 0, "考勤次数");
sheet.addCell(label0);
label0 = new Label(6, 0, "回答问题次数");
sheet.addCell(label0);
label0 = new Label(7, 0, "期末分数");
sheet.addCell(label0);
label0 = new Label(8, 0, "最终分");
sheet.addCell(label0);
// 创建单元格并写入数据
for (GradeVo value : gradeVoList) {
Label label = new Label(0, rowIndex.get(), value.getSNo());
sheet.addCell(label);
label = new Label(1, rowIndex.get(), value.getSName());
sheet.addCell(label);
label = new Label(2, rowIndex.get(), value.getSClass());
sheet.addCell(label);
label = new Label(3, rowIndex.get(), JSON.toJSONString(value.getGrade()));
sheet.addCell(label);
label = new Label(4, rowIndex.get(), value.getAvgScore() + "");
sheet.addCell(label);
label = new Label(5, rowIndex.get(), value.getSignInTimes() + "");
sheet.addCell(label);
label = new Label(6, rowIndex.get(), value.getAnswerTimes() + "");
sheet.addCell(label);
label = new Label(7, rowIndex.get(), value.getFinalScore() + "");
sheet.addCell(label);
label = new Label(8, rowIndex.get(), value.getTotalScore() + "");
sheet.addCell(label);
rowIndex.addAndGet(1);
}
// 写入数据
workbook.write();
// 关闭工作簿
workbook.close();
}catch (WriteException e) {
throw new RuntimeException(e);
}
}
更多用法:
//单元格合并
sheet.mergeCells(0,2,0,3); //把第 0 列第 2 行的单元格和第 0 列第 3 行的单元格合并
//设置单元格列宽和行高
sheet.setColumnView(0,10);//设置第 0 列的宽为10
sheet.setRowView(0,1000);//设置第 0 行的高为10
//插入字符串数据可用String[]数组(注意需要在下面遍历数组!)
String[] str = {"1","2"};
//设置单元格内容样式 设置字体 字体大小 是否加粗
WritableFont font=new WritableFont(WritableFont.createFont("宋体"),12,WritableFont.BOLD);
WritableCellFormat format = new WritableCellFormat(font);
//水平居中
format.setAlignment(jxl.format.Alignment.CENTRE);
方法二: POI
POI (Poor Obfuscation Implementation)主要是指 Apache提供的操作Office的基础工具包(Apache POI),Apache POI 是一个底层的工具类,可实现的功能最全。
缺点:
解析文件时一次性全部加载到内存中,系统并发量不大时可行,一旦并发上来后会产生OOM或者JVM频繁的full gc
操作:
pom文件引入:
引入 Apache POI 和 Apache POI-OOXML 这两个类库,Maven坐标如下:
<dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>4.0.1</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>4.0.1</version> </dependency>
注意:使用HSSF操作.xls格式文件,使用XSSF操作.xlsx文件
读文件:
private final static String xls = "xls";
private final static String xlsx = "xlsx";
/**
* 读入excel文件,解析后返回
* @param file
* @throws IOException
*/
public static List<String[]> readExcel(MultipartFile file) throws IOException {
//检查文件
checkFile(file);
//获得Workbook工作薄对象
Workbook workbook = getWorkBook(file);
//创建返回对象,把每行中的值作为一个数组,所有行作为一个集合返回
List<String[]> list = new ArrayList<String[]>();
if(workbook != null){
for(int sheetNum = 0;sheetNum < workbook.getNumberOfSheets();sheetNum++){
//获得当前sheet工作表
Sheet sheet = workbook.getSheetAt(sheetNum);
if(sheet == null){
continue;
}
//获得当前sheet的开始行
int firstRowNum = sheet.getFirstRowNum();
//获得当前sheet的结束行
int lastRowNum = sheet.getLastRowNum();
//循环除了第一行的所有行
for(int rowNum = firstRowNum+1;rowNum <= lastRowNum;rowNum++){ //为了过滤到第一行因为我的第一行是数据库的列
//获得当前行
Row row = sheet.getRow(rowNum);
if(row == null){
continue;
}
//获得当前行的开始列
int firstCellNum = row.getFirstCellNum();
//获得当前行的列数
int lastCellNum = row.getLastCellNum();//为空列获取
// int lastCellNum = row.getPhysicalNumberOfCells();//为空列不获取
// String[] cells = new String[row.getPhysicalNumberOfCells()];
String[] cells = new String[row.getLastCellNum()];
//循环当前行
for(int cellNum = firstCellNum; cellNum < lastCellNum;cellNum++){
Cell cell = row.getCell(cellNum);
cells[cellNum] = getCellValue(cell);
}
list.add(cells);
}
}
}
logger.info(gson.toJson(list));
return list;
}
public static void checkFile(MultipartFile file) throws IOException{
//判断文件是否存在
if(null == file){
throw new FileNotFoundException("文件不存在!");
}
//获得文件名
String fileName = file.getOriginalFilename();
//判断文件是否是excel文件
if(!fileName.endsWith(xls) && !fileName.endsWith(xlsx)){
throw new IOException(fileName + "不是excel文件");
}
}
public static Workbook getWorkBook(MultipartFile file) {
//获得文件名
String fileName = file.getOriginalFilename();
//创建Workbook工作薄对象,表示整个excel
Workbook workbook = null;
try {
//获取excel文件的io流
InputStream is = file.getInputStream();
//根据文件后缀名不同(xls和xlsx)获得不同的Workbook实现类对象
if(fileName.endsWith(xls)){
//2003
workbook = new HSSFWorkbook(is);
}else if(fileName.endsWith(xlsx)){
//2007
workbook = new XSSFWorkbook(is);
}
} catch (IOException e) {
logger.info(e.getMessage());
}
return workbook;
}
public static String getCellValue(Cell cell){
String cellValue = "";
if(cell == null){
return cellValue;
}
//把数字当成String来读,避免出现1读成1.0的情况
if(cell.getCellType() == Cell.CELL_TYPE_NUMERIC){
cell.setCellType(Cell.CELL_TYPE_STRING);
}
//判断数据的类型
switch (cell.getCellType()){
case Cell.CELL_TYPE_NUMERIC: //数字
cellValue = String.valueOf(cell.getNumericCellValue());
break;
case Cell.CELL_TYPE_STRING: //字符串
cellValue = String.valueOf(cell.getStringCellValue());
break;
case Cell.CELL_TYPE_BOOLEAN: //Boolean
cellValue = String.valueOf(cell.getBooleanCellValue());
break;
case Cell.CELL_TYPE_FORMULA: //公式
// cellValue = String.valueOf(cell.getCellFormula());
cellValue = String.valueOf(cell.getStringCellValue());
break;
case Cell.CELL_TYPE_BLANK: //空值
cellValue = "";
break;
case Cell.CELL_TYPE_ERROR: //故障
cellValue = "非法字符";
break;
default:
cellValue = "未知类型";
break;
}
return cellValue;
}
官网文档:
Busy Developers' Guide to HSSF and XSSF Features
- 工作簿对象支持设置一些全局性质的对象,例如:
- 单元格样式的对象。一个工作簿最多支持6w个单元格的样式;
- 创建字体样式的对象。一个工作簿最多支持32767个 字体样式;
- 工作表对象支持只在单个工作表生效的设置,例如:
- 设置筛选按钮;
- 设置某列的自适应列宽;
- 冻结窗格;
- 拆分窗口;
- 行对象
Row 支持的操作有:
- 创建单元格
- 获取/遍历单元格
- 设置行高
- 获取行号
- 设置/隐藏行
- 单元格对象
单元格对象支持对单个单元格进行设置,例如:
- 获取/设置单元格的值
- 获取/设置单元格的类型(字符串、数值[数字/日期]、布尔值、公式、错误值、空)
- 获取/设置单元格的样式
- 获取/设置超链接
-
单元格的值
单元格的值有7种类型[CellType](数值、字符串、公式、空值、布尔值、错误值)
方法三:EasyExcel
优点:
Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。
easyexcel重写了poi对07版Excel的解析,一个3M的excel用POI sax解析依然需要100M左右内存,改用easyexcel可以降低到几M,并且再大的excel也不会出现内存溢出;03版依赖POI的sax模式,在上层做了模型转换的封装,让使用者更加简单方便
操作:
pom文件:
读Excel:DEMO代码地址:https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java
/**
* 最简单的读
* <p>1. 创建excel对应的实体对象 参照{@link DemoData}
* <p>2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener}
* <p>3. 直接读即可
*/
@Test
public void simpleRead() {
String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();
}
写Excel:DEMO代码地址:https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java
/**
* 最简单的写
* <p>1. 创建excel对应的实体对象 参照{@link com.alibaba.easyexcel.test.demo.write.DemoData}
* <p>2. 直接写即可
*/
@Test
public void simpleWrite() {
String fileName = TestFileUtil.getPath() + "write" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
// 如果这里想使用03 则 传入excelType参数即可
EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());
}
官网文档:
https://easyexcel.opensource.alibaba.com/docs/current/EasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目,在尽可能节约内存的情况下支持读写百M的Excel。https://easyexcel.opensource.alibaba.com/docs/current/读Excel | Easy Excel 官网
三、总结
- 如果操作Excel复杂度高(.xls,.xlsx都存在,且内容格式复杂),建议使用POI。
- 如果操作Excel数据量大并且对对性能有要求,可以使用EasyExcel。