做项目时经常有通过程序读取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:-03-01
*Time:10:13
*Description:示例程序入口类
*/publicclassMainTest{publicstaticvoidmain(String[]args){//设定Excel文件所在路径
StringexcelFileName="/Users/Dreamer-1/Desktop/myBlog/java解析Excel/readExample.xlsx";//读取Excel文件内容
ListreadResult=ExcelReader.readExcel(excelFileName);
//todo进行业务操作
}
}
读取和写入时封装每一“行”数据的ExcelDataVO.java代码如下:/**
*Author:Dreamer-1
*Date:-03-01
*Time:11:33
*Description:读取Excel时,封装读取的每一行的数据
*/publicclassExcelDataVO{/**
*姓名
*/
privateStringname;/**
*年龄
*/
privateIntegerage;/**
*居住地
*/
privateStringlocation;/**
*职业
*/
privateStringjob;publicStringgetName(){returnname;
}publicvoidsetName(Stringname){this.name=name;
}publicIntegergetAge(){returnage;
}publicvoidsetAge(Integerage){this.age=age;
}publicStringgetLocation(){returnlocation;
}publicvoidsetLocation(Stringlocation){this.location=location;
}publicStringgetJob(){returnjob;
}publicvoidsetJob(Stringjob){this.job=job;
}
}
2.2.2 Excel解析类的代码:/**
*Author:Dreamer-1
*Date:-03-01
*Time:10:21
*Description:读取Excel内容
*/publicclassExcelReader{privatestaticLoggerlogger=Logger.getLogger(ExcelReader.class.getName());//日志打印类
privatestaticfinalStringXLS="xls";privatestaticfinalStringXLSX="xlsx";/**
*根据文件后缀名类型获取对应的工作簿对象
*@paraminputStream读取文件的输入流
*@paramfileType文件后缀名类型(xls或xlsx)
*@return包含文件数据的工作簿对象
*@throwsIOException
*/
publicstaticWorkbookgetWorkbook(InputStreaminputStream,StringfileType)throwsIOException{
Workbookworkbook=null;if(fileType.equalsIgnoreCase(XLS)){
workbook=newHSSFWorkbook(inputStream);
}elseif(fileType.equalsIgnoreCase(XLSX)){
workbook=newXSSFWorkbook(inputStream);
}returnworkbook;
}/**
*读取Excel文件内容
*@paramfileName要读取的Excel文件所在路径
*@return读取结果列表,读取失败时返回null
*/
publicstaticListreadExcel(StringfileName){
Workbookworkbook=null;
FileInputStreaminputStream=null;try{//获取Excel后缀名
StringfileType=fileName.substring(fileName.lastIndexOf(".")+1,fileName.length());//获取Excel文件
FileexcelFile=newFile(fileName);if(!excelFile.exists()){
logger.warning("指定的Excel文件不存在!");returnnull;
}//获取Excel工作簿
inputStream=newFileInputStream(excelFile);
workbook=getWorkbook(inputStream,fileType);//读取excel中的数据
ListresultDataList=parseExcel(workbook);returnresultDataList;
}catch(Exceptione){
logger.warning("解析Excel失败,文件名:"+fileName+"错误信息:"+e.getMessage());returnnull;
}finally{try{if(null!=workbook){
workbook.close();
}if(null!=inputStream){
inputStream.close();
}
}catch(Exceptione){
logger.warning("关闭数据流出错!错误信息:"+e.getMessage());returnnull;
}
}
}/**
*解析Excel数据
*@paramworkbookExcel工作簿对象
*@return解析结果
*/
privatestaticListparseExcel(Workbookworkbook){
ListresultDataList=newArrayList<>();//解析sheet
for(intsheetNum=0;sheetNum
Sheetsheet=workbook.getSheetAt(sheetNum);//校验sheet是否合法
if(sheet==null){continue;
}//获取第一行数据
intfirstRowNum=sheet.getFirstRowNum();
RowfirstRow=sheet.getRow(firstRowNum);if(null==firstRow){
logger.warning("解析Excel失败,在第一行没有读取到任何数据!");
}//解析每一行的数据,构造数据对象
introwStart=firstRowNum+1;introwEnd=sheet.getPhysicalNumberOfRows();for(introwNum=rowStart;rowNum
Rowrow=sheet.getRow(rowNum);if(null==row){continue;
}
ExcelDataVOresultData=convertRowToData(row);if(null==resultData){
logger.warning("第"+row.getRowNum()+"行数据不合法,已忽略!");continue;
}
resultDataList.add(resultData);
}
}returnresultDataList;
}/**
*将单元格内容转换为字符串
*@paramcell
*@return
*/
privatestaticStringconvertCellValueToString(Cellcell){if(cell==null){returnnull;
}
StringreturnValue=null;switch(cell.getCellType()){caseNUMERIC://数字
DoubledoubleValue=cell.getNumericCellValue();//格式化科学计数法,取一位整数
DecimalFormatdf=newDecimalFormat("0");
returnValue=df.format(doubleValue);break;caseSTRING://字符串
returnValue=cell.getStringCellValue();break;caseBOOLEAN://布尔
BooleanbooleanValue=cell.getBooleanCellValue();
returnValue=booleanValue.toString();break;caseBLANK://空值
break;caseFORMULA://公式
returnValue=cell.getCellFormula();break;caseERROR://故障
break;default:break;
}returnreturnValue;
}/**
*提取每一行中需要的数据,构造成为一个结果数据对象
*
*当该行中有单元格的数据为空或不合法时,忽略该行的数据
*
*@paramrow行数据
*@return解析后的行数据对象,行数据错误时返回null
*/
privatestaticExcelDataVOconvertRowToData(Rowrow){
ExcelDataVOresultData=newExcelDataVO();
Cellcell;intcellNum=0;//获取姓名
cell=row.getCell(cellNum++);
Stringname=convertCellValueToString(cell);
resultData.setName(name);//获取年龄
cell=row.getCell(cellNum++);
StringageStr=convertCellValueToString(cell);if(null==ageStr||"".equals(ageStr)){//年龄为空
resultData.setAge(null);
}else{
resultData.setAge(Integer.parseInt(ageStr));
}//获取居住地
cell=row.getCell(cellNum++);
Stringlocation=convertCellValueToString(cell);
resultData.setLocation(location);//获取职业
cell=row.getCell(cellNum++);
Stringjob=convertCellValueToString(cell);
resultData.setJob(job);returnresultData;
}
}
2.2.3 应用场景补充
一般我们会有这样的应用场景,即:在前台页面的文件上传入口上传本地的Excel文件到后台,后台收到Excel文件后进行解析并做对应的业务操作;
这里假设前台已经有了上传文件的入口,再简单贴一下后台的解析代码;
后台接收前台数据的Controller层代码示例:@PostMapping("/uploadExcel")publicResponseEntity>uploadImage(MultipartFilefile){//检查前台数据合法性
if(null==file||file.isEmpty()){
logger.warning("上传的Excel商品数据文件为空!上传时间:"+newDate());returnnewResponseEntity<>(HttpStatus.BAD_REQUEST);
}try{//解析Excel
ListparsedResult=ExcelReader.readExcel(file);//todo进行业务操作
returnnewResponseEntity<>(HttpStatus.OK);
}catch(Exceptione){
logger.warning("上传的Excel商品数据文件为空!上传时间:"+newDate());returnnewResponseEntity<>(HttpStatus.BAD_REQUEST);
}
}
ExcelReader.java中的 readExcel() 方法需要做一定的修改,代码如下:/**
*读取Excel文件内容
*@paramfile上传的Excel文件
*@return读取结果列表,读取失败时返回null
*/
publicstaticListreadExcel(MultipartFilefile){
Workbookworkbook=null;try{//获取Excel后缀名
StringfileName=file.getOriginalFilename();if(fileName==null||fileName.isEmpty()||fileName.lastIndexOf(".")
logger.warning("解析Excel失败,因为获取到的Excel文件名非法!");returnnull;
}
StringfileType=fileName.substring(fileName.lastIndexOf(".")+1,fileName.length());//获取Excel工作簿
workbook=getWorkbook(file.getInputStream(),fileType);//读取excel中的数据
ListresultDataList=parseExcel(workbook);returnresultDataList;
}catch(Exceptione){
logger.warning("解析Excel失败,文件名:"+file.getOriginalFilename()+"错误信息:"+e.getMessage());returnnull;
}finally{try{if(null!=workbook){
workbook.close();
}
}catch(Exceptione){
logger.warning("关闭数据流出错!错误信息:"+e.getMessage());returnnull;
}
}
}
2.3 写入数据
写入数据时主要调用ExcelWriter.java来创建Excel工作簿对象并写入数据,这里以写入系统文件数据为例演示写入的过程:
2.3.1 主程序入口类代码/**
*Author:Dreamer-1
*Date:-03-01
*Time:10:13
*Description:示例程序入口类
*/publicclassMainTest{privatestaticLoggerlogger=Logger.getLogger(MainTest.class.getName());publicstaticvoidmain(String[]args){//创建需要写入的数据列表
ListdataVOList=newArrayList<>(2);
ExcelDataVOdataVO=newExcelDataVO();
dataVO.setName("小明");
dataVO.setAge(18);
dataVO.setLocation("广州");
dataVO.setJob("大学生");
ExcelDataVOdataVO2=newExcelDataVO();
dataVO2.setName("小花");
dataVO2.setAge(19);
dataVO2.setLocation("深圳");
dataVO2.setJob("大学生");
dataVOList.add(dataVO);
dataVOList.add(dataVO2);//写入数据到工作簿对象内
Workbookworkbook=ExcelWriter.exportData(dataVOList);//以文件的形式输出工作簿对象
FileOutputStreamfileOut=null;try{
StringexportFilePath="/Users/Dreamer-1/Desktop/myBlog/java解析Excel/writeExample.xlsx";
FileexportFile=newFile(exportFilePath);if(!exportFile.exists()){
exportFile.createNewFile();
}
fileOut=newFileOutputStream(exportFilePath);
workbook.write(fileOut);
fileOut.flush();
}catch(Exceptione){
logger.warning("输出Excel时发生错误,错误原因:"+e.getMessage());
}finally{try{if(null!=fileOut){
fileOut.close();
}if(null!=workbook){
workbook.close();
}
}catch(IOExceptione){
logger.warning("关闭输出流时发生错误,错误原因:"+e.getMessage());
}
}
}
}
2.3.2 写入Excel类的代码
ExcelWriter.java类中,你可以根据实际需要替换 CELL_HEADS 列头的信息,然后重写 convertDataToRow 方法,转换你自己的行数据;/**
*Author:Dreamer-1
*Date:-03-01
*Time:11:09
*Description:生成Excel并写入数据
*/publicclassExcelWriter{privatestaticListCELL_HEADS;//列头
static{//类装载时就载入指定好的列头信息,如有需要,可以考虑做成动态生成的列头
CELL_HEADS=newArrayList<>();
CELL_HEADS.add("姓名");
CELL_HEADS.add("年龄");
CELL_HEADS.add("居住城市");
CELL_HEADS.add("职业");
}/**
*生成Excel并写入数据信息
*@paramdataList数据列表
*@return写入数据后的工作簿对象
*/
publicstaticWorkbookexportData(ListdataList){//生成xlsx的Excel
Workbookworkbook=newSXSSFWorkbook();//如需生成xls的Excel,请使用下面的工作簿对象,注意后续输出时文件后缀名也需更改为xls
//Workbookworkbook=newHSSFWorkbook();
//生成Sheet表,写入第一行的列头
Sheetsheet=buildDataSheet(workbook);//构建每行的数据内容
introwNum=1;for(Iteratorit=dataList.iterator();it.hasNext();){
ExcelDataVOdata=it.next();if(data==null){continue;
}//输出行数据
Rowrow=sheet.createRow(rowNum++);
convertDataToRow(data,row);
}returnworkbook;
}/**
*生成sheet表,并写入第一行数据(列头)
*@paramworkbook工作簿对象
*@return已经写入列头的Sheet
*/
privatestaticSheetbuildDataSheet(Workbookworkbook){
Sheetsheet=workbook.createSheet();//设置列头宽度
for(inti=0;i
sheet.setColumnWidth(i,4000);
}//设置默认行高
sheet.setDefaultRowHeight((short)400);//构建头单元格样式
CellStylecellStyle=buildHeadCellStyle(sheet.getWorkbook());//写入第一行各列的数据
Rowhead=sheet.createRow(0);for(inti=0;i
Cellcell=head.createCell(i);
cell.setCellValue(CELL_HEADS.get(i));
cell.setCellStyle(cellStyle);
}returnsheet;
}/**
*设置第一行列头的样式
*@paramworkbook工作簿对象
*@return单元格样式对象
*/
privatestaticCellStylebuildHeadCellStyle(Workbookworkbook){
CellStylestyle=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);//粗体字设置
Fontfont=workbook.createFont();
font.setBold(true);
style.setFont(font);returnstyle;
}/**
*将数据转换成行
*@paramdata源数据
*@paramrow行对象
*@return
*/
privatestaticvoidconvertDataToRow(ExcelDataVOdata,Rowrow){intcellNum=0;
Cellcell;//姓名
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")publicvoidexportExcel(HttpServletRequestrequest,HttpServletResponseresponse){
Workbookworkbook=null;
OutputStreamout=null;try{//todo根据业务需求获取需要写入Excel的数据列表dataList
//生成Excel工作簿对象并写入数据
workbook=ExcelWriter.exportData(dataList);//写入Excel文件到前端
if(null!=workbook){
StringexcelName="示例Excel导出";
StringfileName=excelName+DateUtil.format(newDate(),DateUtil.SHORT_DATE)+".xlsx";
fileName=newString(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(Exceptione){
logger.WARNING("写入Excel过程出错!错误原因:"+e.getMessage());
}finally{try{if(null!=workbook){
workbook.close();
}if(null!=out){
out.close();
}
}catch(IOExceptione){
logger.WARNING("关闭workbook或outputStream出错!");
}
}
}//前台页面发送请求到后台Controller时的JS代码可参考:varurl="/exportExcel";
window.location=url;
3. 源码下载