做项目时经常有通过程序读取Excel数据,或是创建新的Excel并写入数据的需求;
网上很多经验教程里使用的POI版本都比较老了,一些API在新版里已经废弃,这里基于最新的Apache POI 4.0.1版本来总结一下整个读取和写入Excel的过程,希望能帮助到需要的人 ^_^
1. 准备工作
1.1 在项目中引入Apache POI相关类库
引入 Apache POI 和 Apache POI-OOXML 这两个类库,Maven坐标如下:
org.apache.poi
poi
4.0.1
org.apache.poi
poi-ooxml
4.0.1
2. 读取或写入Excel数据
2.1 示例程序结构说明
简单说明一下示例程序的整体结构:ExcelReader.java是实现读取Excel数据功能的类;
ExcelWriter.java是创建新的Excel并向其中写入数据的类;
ExcelDataVO.java封装了读取或写入时每一“行”的数据;
MainTest.java是示例程序的入口类,其中演示了读取和写入Excel数据的整个过程;
2.2 读取数据
示例程序需要从桌面读取 readExample.xlsx 内的数据,readExample.xlsx 的内容如下:
读取Excel时主要调用ExcelReader.java类来读取和解析Excel的具体内容,这里以读取系统文件的形式演示读取过程:(兼容 xls 和 xlsx)
2.2.1 主程序入口类代码:/**
* Author: Dreamer-1
* Date: 2019-03-01
* Time: 10:13
* Description: 示例程序入口类
*/public class MainTest { public static void main(String[] args) { // 设定Excel文件所在路径
String excelFileName = "/Users/Dreamer-1/Desktop/myBlog/java解析Excel/readExample.xlsx"; // 读取Excel文件内容
List readResult = ExcelReader.readExcel(excelFileName);
// todo 进行业务操作
}
}
读取和写入时封装每一“行”数据的ExcelDataVO.java代码如下:/**
* Author: Dreamer-1
* Date: 2019-03-01
* Time: 11:33
* Description: 读取Excel时,封装读取的每一行的数据
*/public class ExcelDataVO { /**
* 姓名
*/
private String name; /**
* 年龄
*/
private Integer age; /**
* 居住地
*/
private String location; /**
* 职业
*/
private String job; public String getName() { return name;
} public void setName(String name) { this.name = name;
} public Integer getAge() { return age;
} public void setAge(Integer age) { this.age = age;
} public String getLocation() { return location;
} public void setLocation(String location) { this.location = location;
} public String getJob() { return job;
} public void setJob(String job) { this.job = job;
}
}
2.2.2 Excel解析类的代码:/**
* Author: Dreamer-1
* Date: 2019-03-01
* Time: 10:21
* Description: 读取Excel内容
*/public class ExcelReader { private static Logger logger = Logger.getLogger(ExcelReader.class.getName()); // 日志打印类
private static final String XLS = "xls"; private static final String XLSX = "xlsx"; /**
* 根据文件后缀名类型获取对应的工作簿对象
* @param inputStream 读取文件的输入流
* @param fileType 文件后缀名类型(xls或xlsx)
* @return 包含文件数据的工作簿对象
* @throws IOException
*/
public static Workbook getWorkbook(InputStream inputStream, String fileType) throws IOException {
Workbook workbook = null; if (fileType.equalsIgnoreCase(XLS)) {
workbook = new HSSFWorkbook(inputStream);
} else if (fileType.equalsIgnoreCase(XLSX)) {
workbook = new XSSFWorkbook(inputStream);
} return workbook;
} /**
* 读取Excel文件内容
* @param fileName 要读取的Excel文件所在路径
* @return 读取结果列表,读取失败时返回null
*/
public static List readExcel(String fileName) {
Workbook workbook = null;
FileInputStream inputStream = null; try { // 获取Excel后缀名
String fileType = fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length()); // 获取Excel文件
File excelFile = new File(fileName); if (!excelFile.exists()) {
logger.warning("指定的Excel文件不存在!"); return null;
} // 获取Excel工作簿
inputStream = new FileInputStream(excelFile);
workbook = getWorkbook(inputStream, fileType); // 读取excel中的数据
List resultDataList = parseExcel(workbook); return resultDataList;
} catch (Exception e) {
logger.warning("解析Excel失败,文件名:" + fileName + " 错误信息:" + e.getMessage()); return null;
} finally { try { if (null != workbook) {
workbook.close();
} if (null != inputStream) {
inputStream.close();
}
} catch (Exception e) {
logger.warning("关闭数据流出错!错误信息:" + e.getMessage()); return null;
}
}
} /**
* 解析Excel数据
* @param workbook Excel工作簿对象
* @return 解析结果
*/
private static List parseExcel(Workbook workbook) {
List resultDataList = new ArrayList<>(); // 解析sheet
for (int sheetNum = 0; sheetNum
Sheet sheet = workbook.getSheetAt(sheetNum); // 校验sheet是否合法
if (sheet == null) { continue;
} // 获取第一行数据
int firstRowNum = sheet.getFirstRowNum();
Row firstRow = sheet.getRow(firstRowNum); if (null == firstRow) {
logger.warning("解析Excel失败,在第一行没有读取到任何数据!");
} // 解析每一行的数据,构造数据对象
int rowStart = firstRowNum + 1; int rowEnd = sheet.getPhysicalNumberOfRows(); for (int rowNum = rowStart; rowNum
Row row = sheet.getRow(rowNum); if (null == row) { continue;
}
ExcelDataVO resultData = convertRowToData(row); if (null == resultData) {
logger.warning("第 " + row.getRowNum() + "行数据不合法,已忽略!"); continue;
}
resultDataList.add(resultData);
}
} return resultDataList;
} /**
* 将单元格内容转换为字符串
* @param cell
* @return
*/
private static String convertCellValueToString(Cell cell) { if(cell==null){ return null;
}
String returnValue = null; switch (cell.getCellType()) { case NUMERIC: //数字
Double doubleValue = cell.getNumericCellValue(); // 格式化科学计数法,取一位整数
DecimalFormat df = new DecimalFormat("0");
returnValue = df.format(doubleValue); break; case STRING: //字符串
returnValue = cell.getStringCellValue(); break; case BOOLEAN: //布尔
Boolean booleanValue = cell.getBooleanCellValue();
returnValue = booleanValue.toString(); break; case BLANK: // 空值
break; case FORMULA: // 公式
returnValue = cell.getCellFormula(); break; case ERROR: // 故障
break; default: break;
} return returnValue;
} /**
* 提取每一行中需要的数据,构造成为一个结果数据对象
*
* 当该行中有单元格的数据为空或不合法时,忽略该行的数据
*
* @param row 行数据
* @return 解析后的行数据对象,行数据错误时返回null
*/
private static ExcelDataVO convertRowToData(Row row) {
ExcelDataVO resultData = new ExcelDataVO();
Cell cell; int cellNum = 0; // 获取姓名
cell = row.getCell(cellNum++);
String name = convertCellValueToString(cell);
resultData.setName(name); // 获取年龄
cell = row.getCell(cellNum++);
String ageStr = convertCellValueToString(cell); if (null == ageStr || "".equals(ageStr)) { // 年龄为空
resultData.setAge(null);
} else {
resultData.setAge(Integer.parseInt(ageStr));
} // 获取居住地
cell = row.getCell(cellNum++);
String location = convertCellValueToString(cell);
resultData.setLocation(location); // 获取职业
cell = row.getCell(cellNum++);
String job = convertCellValueToString(cell);
resultData.setJob(job); return resultData;
}
}
2.2.3 应用场景补充
一般我们会有这样的应用场景,即:在前台页面的文件上传入口上传本地的Excel文件到后台,后台收到Excel文件后进行解析并做对应的业务操作;
这里假设前台已经有了上传文件的入口,再简单贴一下后台的解析代码;
后台接收前台数据的Controller层代码示例:@PostMapping("/uploadExcel") public ResponseEntity> uploadImage(MultipartFile file) { // 检查前台数据合法性
if (null == file || file.isEmpty()) {
logger.warning("上传的Excel商品数据文件为空!上传时间:" + new Date()); return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
} try { // 解析Excel
List parsedResult = ExcelReader.readExcel(file); // todo 进行业务操作
return new ResponseEntity<>(HttpStatus.OK);
} catch (Exception e) {
logger.warning("上传的Excel商品数据文件为空!上传时间:" + new Date()); return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
}
ExcelReader.java中的 readExcel() 方法需要做一定的修改,代码如下:/**
* 读取Excel文件内容
* @param file 上传的Excel文件
* @return 读取结果列表,读取失败时返回null
*/
public static List readExcel(MultipartFile file) {
Workbook workbook = null; try { // 获取Excel后缀名
String fileName = file.getOriginalFilename(); if (fileName == null || fileName.isEmpty() || fileName.lastIndexOf(".")
logger.warning("解析Excel失败,因为获取到的Excel文件名非法!"); return null;
}
String fileType = fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length()); // 获取Excel工作簿
workbook = getWorkbook(file.getInputStream(), fileType); // 读取excel中的数据
List resultDataList = parseExcel(workbook); return resultDataList;
} catch (Exception e) {
logger.warning("解析Excel失败,文件名:" + file.getOriginalFilename() + " 错误信息:" + e.getMessage()); return null;
} finally { try { if (null != workbook) {
workbook.close();
}
} catch (Exception e) {
logger.warning("关闭数据流出错!错误信息:" + e.getMessage()); return null;
}
}
}
2.3 写入数据
写入数据时主要调用ExcelWriter.java来创建Excel工作簿对象并写入数据,这里以写入系统文件数据为例演示写入的过程:
2.3.1 主程序入口类代码/**
* Author: Dreamer-1
* Date: 2019-03-01
* Time: 10:13
* Description: 示例程序入口类
*/public class MainTest { private static Logger logger = Logger.getLogger(MainTest.class.getName()); public static void main(String[] args) { // 创建需要写入的数据列表
List dataVOList = new ArrayList<>(2);
ExcelDataVO dataVO = new ExcelDataVO();
dataVO.setName("小明");
dataVO.setAge(18);
dataVO.setLocation("广州");
dataVO.setJob("大学生");
ExcelDataVO dataVO2 = new ExcelDataVO();
dataVO2.setName("小花");
dataVO2.setAge(19);
dataVO2.setLocation("深圳");
dataVO2.setJob("大学生");
dataVOList.add(dataVO);
dataVOList.add(dataVO2); // 写入数据到工作簿对象内
Workbook workbook = ExcelWriter.exportData(dataVOList); // 以文件的形式输出工作簿对象
FileOutputStream fileOut = null; try {
String exportFilePath = "/Users/Dreamer-1/Desktop/myBlog/java解析Excel/writeExample.xlsx";
File exportFile = new File(exportFilePath); if (!exportFile.exists()) {
exportFile.createNewFile();
}
fileOut = new FileOutputStream(exportFilePath);
workbook.write(fileOut);
fileOut.flush();
} catch (Exception e) {
logger.warning("输出Excel时发生错误,错误原因:" + e.getMessage());
} finally { try { if (null != fileOut) {
fileOut.close();
} if (null != workbook) {
workbook.close();
}
} catch (IOException e) {
logger.warning("关闭输出流时发生错误,错误原因:" + e.getMessage());
}
}
}
}
2.3.2 写入Excel类的代码
ExcelWriter.java类中,你可以根据实际需要替换 CELL_HEADS 列头的信息,然后重写 convertDataToRow 方法,转换你自己的行数据;/**
* Author: Dreamer-1
* Date: 2019-03-01
* Time: 11:09
* Description: 生成Excel并写入数据
*/public class ExcelWriter { private static List CELL_HEADS; //列头
static{ // 类装载时就载入指定好的列头信息,如有需要,可以考虑做成动态生成的列头
CELL_HEADS = new ArrayList<>();
CELL_HEADS.add("姓名");
CELL_HEADS.add("年龄");
CELL_HEADS.add("居住城市");
CELL_HEADS.add("职业");
} /**
* 生成Excel并写入数据信息
* @param dataList 数据列表
* @return 写入数据后的工作簿对象
*/
public static Workbook exportData(List dataList){ // 生成xlsx的Excel
Workbook workbook = new SXSSFWorkbook(); // 如需生成xls的Excel,请使用下面的工作簿对象,注意后续输出时文件后缀名也需更改为xls
//Workbook workbook = new HSSFWorkbook();
// 生成Sheet表,写入第一行的列头
Sheet sheet = buildDataSheet(workbook); //构建每行的数据内容
int rowNum = 1; for (Iterator it = dataList.iterator(); it.hasNext(); ) {
ExcelDataVO data = it.next(); if (data == null) { continue;
} //输出行数据
Row row = sheet.createRow(rowNum++);
convertDataToRow(data, row);
} return workbook;
} /**
* 生成sheet表,并写入第一行数据(列头)
* @param workbook 工作簿对象
* @return 已经写入列头的Sheet
*/
private static Sheet buildDataSheet(Workbook workbook) {
Sheet sheet = workbook.createSheet(); // 设置列头宽度
for (int i=0; i
sheet.setColumnWidth(i, 4000);
} // 设置默认行高
sheet.setDefaultRowHeight((short) 400); // 构建头单元格样式
CellStyle cellStyle = buildHeadCellStyle(sheet.getWorkbook()); // 写入第一行各列的数据
Row head = sheet.createRow(0); for (int i = 0; i
Cell cell = head.createCell(i);
cell.setCellValue(CELL_HEADS.get(i));
cell.setCellStyle(cellStyle);
} return sheet;
} /**
* 设置第一行列头的样式
* @param workbook 工作簿对象
* @return 单元格样式对象
*/
private static CellStyle buildHeadCellStyle(Workbook workbook) {
CellStyle style = workbook.createCellStyle(); //对齐方式设置
style.setAlignment(HorizontalAlignment.CENTER); //边框颜色和宽度设置
style.setBorderBottom(BorderStyle.THIN);
style.setBottomBorderColor(IndexedColors.BLACK.getIndex()); // 下边框
style.setBorderLeft(BorderStyle.THIN);
style.setLeftBorderColor(IndexedColors.BLACK.getIndex()); // 左边框
style.setBorderRight(BorderStyle.THIN);
style.setRightBorderColor(IndexedColors.BLACK.getIndex()); // 右边框
style.setBorderTop(BorderStyle.THIN);
style.setTopBorderColor(IndexedColors.BLACK.getIndex()); // 上边框
//设置背景颜色
style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
style.setFillPattern(FillPatternType.SOLID_FOREGROUND); //粗体字设置
Font font = workbook.createFont();
font.setBold(true);
style.setFont(font); return style;
} /**
* 将数据转换成行
* @param data 源数据
* @param row 行对象
* @return
*/
private static void convertDataToRow(ExcelDataVO data, Row row){ int cellNum = 0;
Cell cell; // 姓名
cell = row.createCell(cellNum++);
cell.setCellValue(null == data.getName() ? "" : data.getName()); // 年龄
cell = row.createCell(cellNum++); if (null != data.getAge()) {
cell.setCellValue(data.getAge());
} else {
cell.setCellValue("");
} // 所在城市
cell = row.createCell(cellNum++);
cell.setCellValue(null == data.getLocation() ? "" : data.getLocation()); // 职业
cell = row.createCell(cellNum++);
cell.setCellValue(null == data.getJob() ? "" : data.getJob());
}
}
示例程序运行后将会在指定的系统路径下生成 writeExample.xlsx文件,其内容如下:
2.3.3 应用场景补充
一般写入Excel时会有这样的场景:前台页面上有一个导出按钮,点击后将后台某张表里的数据以Excel的形式导出,导出的Excel文件通过浏览器下载到用户系统中;
这里默认前台页面已经有相应的按钮功能,给出对应的Controller层代码供参考:@GetMapping("/exportExcel") public void exportExcel(HttpServletRequest request, HttpServletResponse response) {
Workbook workbook = null;
OutputStream out = null; try { // todo 根据业务需求获取需要写入Excel的数据列表 dataList
// 生成Excel工作簿对象并写入数据
workbook = ExcelWriter.exportData(dataList); // 写入Excel文件到前端
if(null != workbook){
String excelName = "示例Excel导出";
String fileName = excelName + DateUtil.format(new Date(), DateUtil.SHORT_DATE) + ".xlsx";
fileName = new String(fileName.getBytes("UTF-8"),"iso8859-1");
response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
response.setContentType("application/x-download");
response.setCharacterEncoding("UTF-8");
response.addHeader("Pargam", "no-cache");
response.addHeader("Cache-Control", "no-cache");
response.flushBuffer();
out = response.getOutputStream();
workbook.write(out);
out.flush();
}
} catch (Exception e) {
logger.WARNING("写入Excel过程出错!错误原因:" + e.getMessage());
} finally { try { if (null != workbook) {
workbook.close();
} if (null != out) {
out.close();
}
} catch (IOException e) {
logger.WARNING("关闭workbook或outputStream出错!");
}
}
}// 前台页面发送请求到后台Controller时的JS代码可参考:var url = "/exportExcel";
window.location=url;
3. 源码下载