Java利用POI读取、写入Excel的方法指南
前言
apache poi [1] 是用java编写的免费开源的跨平台的 java api,apache poi提供api给java程式对microsoft office格式档案读和写的功能。poi为“poor obfuscation implementation”的首字母缩写,意为“简洁版的模糊实现”。
做项目时经常有通过程序读取excel数据,或是创建新的excel并写入数据的需求;
网上很多经验教程里使用的poi版本都比较老了,一些api在新版里已经废弃,这里基于最新的apache poi 4.0.1版本来总结一下整个读取和写入excel的过程,希望能帮助到需要的人 ^_^
1. 准备工作
1.1 在项目中引入apache poi相关类库
引入 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>
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<exceldatavo> 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<exceldatavo> 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<exceldatavo> 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<exceldatavo> parseexcel(workbook workbook) { list<exceldatavo> resultdatalist = new arraylist<>(); // 解析sheet for (int sheetnum = 0; sheetnum < workbook.getnumberofsheets(); 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 < rowend; 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<exceldatavo> 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<exceldatavo> readexcel(multipartfile file) { workbook workbook = null; try { // 获取excel后缀名 string filename = file.getoriginalfilename(); if (filename == null || filename.isempty() || filename.lastindexof(".") < 0) { logger.warning("解析excel失败,因为获取到的excel文件名非法!"); return null; } string filetype = filename.substring(filename.lastindexof(".") + 1, filename.length()); // 获取excel工作簿 workbook = getworkbook(file.getinputstream(), filetype); // 读取excel中的数据 list<exceldatavo> 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<exceldatavo> 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<string> 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<exceldatavo> datalist){ // 生成xlsx的excel workbook workbook = new sxssfworkbook(); // 如需生成xls的excel,请使用下面的工作簿对象,注意后续输出时文件后缀名也需更改为xls //workbook workbook = new hssfworkbook(); // 生成sheet表,写入第一行的列头 sheet sheet = builddatasheet(workbook); //构建每行的数据内容 int rownum = 1; for (iterator<exceldatavo> 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<cell_heads.size(); 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_heads.size(); 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. 源码下载
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。
下一篇: MySQL中从库延迟状况排查的一则案例