POI 技术如何实现对 Word 和 Excel 的读写操作?POI 技术相对其他同类型技术的优劣势又是哪些?怎样实现复杂的 Excel 读写操作?POI 对于 Word 和 Excel 有足够友好吗?这个 Chat,带领大家使用免费却实用的 POI 技术,实现几种日常通用的业务台账操作。
本场 Chat 主要内容:
Apache POI 是用 Java 编写的免费开源的跨平台的 Java API,Apache POI 提供 API 给 Java 程式对 Microsoft Office(Excel、WORD、PowerPoint、Visio 等,主要实现用于 Excel)格式档案读和写的功能,POI 为 “ Poor Obfuscation Implementation ” 的首字母缩写,意为“简洁版的模糊实现”。
Microsoft 的 Office 系列产品拥有大量的用户,其中 Word、Excel 也成为办公文件的首选。在 Java 中,已经有很多对于 Word、Excel 的读写的解决方案,其中开源免费好用、用户量较大的就是 Apache 的 POI。
官方网站:
http://poi.apache.org/index.html
API文档:
http://poi.apache.org/apidocs/index.html
Office 系列产品的 java 读写插件的项目有很多:
docx4j:是一个解压的 docx 包(docx 本身是 zip 包)和解析 WordprocessingML 格式 XML 的 Java 库 。 最新版本的 docx4j 也支持 PowerPoint pptx 文件。但方法实现过于底层,国内相关文档说明特别少,而很少被人熟知。
PageOffice:国产的 Office 插件,虽然功能接口虽然没有没有 poi 的多,但是开发调用简单,特别是对 word 的读写操作比 POI 好用,毕竟 POI 的中文文档太少,经常拿来用的就是做在线预览了。不过要安装 PageOffice 控件,收费。
Jxl:开源世界中,有两套比较有影响的 API 可供使用,一个是 POI,一个是 jExcelAPI。纯 Java 的,并不依赖 Windows 系统,即使运行在 Linux下,它同样能够正确的处理 Excel 文件。图形和图表的支持很有限,而且仅仅识别 PNG 格式。网上有人做过测试,jxl 内存消耗也会更小,大数据量的时候建议使用 jxl,但是实现的功能 POI 比 jxl 更加完善。功能复杂或是有拓展需求的,建议使用 POI。
POI:相较于其他插件,POI 的用户量是最多的。简单易用,功能完善,项目开源,对 Excel 的读写操作功能十分强大,设置到单元格样式、标注脚注、设置打印 、插入图片、超链接等等,基本满足业务的所有需求。(网上有人说 POI 会出现莫名的 bug,数据替换参数总有失败,暂时没发现这种 bug了。)不过 POI 操作 word 的时候,只能创建简单的 word 文档,不过样式文字的读写操作也是完全满足的,只是相较操作 Excel 不算友好。POI 导出数据量过大的时候,容易造成内存溢出。
HSSF:提供读写 Microsoft Excel XLS 格式档案的功能。
XSSF:提供读写 Microsoft Excel OOXML XLSX 格式档案的功能。
HWPF:提供读写 Microsoft Word DOC 格式档案的功能。
HSLF:提供读写 Microsoft PowerPoint 格式档案的功能。
HDGF:提供读 Microsoft Visio 格式档案的功能。
HPBF:提供读 Microsoft Publisher 格式档案的功能。
HSMF:提供读 Microsoft Outlook 格式档案的功能。
HSSFWorkbook excel文档对象。
HSSFSheet excel的sheet HSSFRow excel的行。
HSSFCell excel的单元格 HSSFFont excel字体。
HSSFName 名称 HSSFDataFormat 日期格式。
HSSFHeader sheet头。
HSSFFooter sheet尾。
HSSFCellStyle cell样式。
HSSFDateUtil 日期。
HSSFPrintSetup 打印。
HSSFErrorConstants 错误信息表。
所用插件:poi - 3.17。下面实现一个简单的Excel导出。
思路:
获取一个已存在的 Excel 工作簿对象模板(也可以创建新的 Excel 工作簿对象,我习惯使用一个已存在的模板,这样 Excel 标题,文档摘要一些固定模板的就不用写了) -> 获取 Excel 工作表对象 -> 创建 Excel 工作表的行 -> 创建单元格样式 -> 创建 Excel 工作表指定行的单元格 -> 设置 Excel 工作表的值 -> 保存 Excel 文件(使用输出流)代码实现:
1,获取一个已存在的 Excel 工作簿对象模板:
FileInputStream iStream = new FileInputStream("C:\\Users\\Administrator\\Desktop\\POI\\sample.xls");//里面的变量就是模板所在路径了 HSSFWorkbook workbook = new HSSFWorkbook(iStream);
2,获取 Excel 工作表对象:
HSSFSheet sheet = workbook.getSheetAt(0);
3,创建 Excel 工作表的行 :
HSSFRow row = sheet.createRow(0);
4,创建单元格样式 :
4.1,创建字体样式:
HSSFFont headFont = workbook.createFont(); headFont.setFontName('宋体')headFont.setFontHeightInPoints((short)12) //字体大小headFont.setBoldweight(headFont1.BOLDWEIGHT_BOLD); //加粗
4.2单元格样式:
HSSFCellStyle cell_Style= workbook.createCellStyle();cell_Style.setFont(headFont); //设置字体样式cell_Style.setAlignment(CellStyle.ALIGN_LEFT); //设置单元格左对齐
5,创建 Excel 工作表指定行的单元格:
HSSFCell cell=row.createCell(0);
5.1,使用单元格样式:
cell.setCellStyle(cell_Style);
6,设置 Excel 工作表的值(单元格的值):
cell.setCellValue(“单元格里面的值”);
7,保存 Excel 文件(使用输出流):
FileOutputStream fileOut = new FileOutputStream(path); workbook.write(fileOut);fileOut.close();//关闭文件流
上面 7 点要素做到,就可以做到导出一个 Excel 了。下面是个例子的代码(注意一点是,我用的是动态语言 groovy,用 java 的变量类型改一下):
//导入文件、单元格样式 def getSheet(){ try { FileInputStream iStream = new FileInputStream("C:\\Users\\Administrator\\Desktop\\POI\\sample.xls"); HSSFWorkbook workbook = new HSSFWorkbook(iStream); HSSFSheet sheet = workbook.getSheetAt(0); sheet.setDefaultColumnWidth(50); //设置标题字体为宋体,14号字,加粗 def headFont = workbook.createFont() headFont.setFontName('宋体') headFont.setFontHeightInPoints((short)14) headFont.setBoldweight(headFont.BOLDWEIGHT_BOLD); //加粗 HSSFCellStyle cell_Style = workbook.createCellStyle() cell_Style.setFont(headFont) //设置字体样式 cell_Style.setAlignment(CellStyle.ALIGN_LEFT) //左对齐 cell_Style.setVerticalAlignment(CellStyle.VERTICAL_CENTER) //垂直居中 cell_Style.setWrapText(true); //自动换行 cell_Style.setBorderLeft(HSSFCellStyle.BORDER_THIN); //边框加线 cell_Style.setBorderRight(HSSFCellStyle.BORDER_THIN); //边框加线 cell_Style.setBorderTop(HSSFCellStyle.BORDER_THIN); //边框加线 cell_Style.setBorderBottom(HSSFCellStyle.BORDER_THIN); //边框加线 return [workbook:workbook,sheet:sheet,cell_Style:cell_Style]; }catch (Exception e){ println("导出文件失败!"); println(e.toString()); }finally{ try{ if(iStream){iStream.close();} }catch(Exception e){ println(e.toString()); } } } //导出Excel def down_excel(){ try { def data=[["小三","发小"],["小玲","妹纸"],["小7","宅家里"]]; def obj=getSheet(); def workbook=obj.workbook; def sheet =obj.sheet; def cell; def row; data.eachWithIndex{d,i -> row=sheet.createRow(i+2); cell=row.createCell(0);cell.setCellStyle(obj.cell_Style);cell.setCellValue(i+1); cell=row.createCell(1);cell.setCellStyle(obj.cell_Style);cell.setCellValue(d[0]); cell=row.createCell(2);cell.setCellStyle(obj.cell_Style);cell.setCellValue(d[1]); } def downname=encodeFileName("导出Excel("+(new Date().format('yyyy-MM-dd'))+").xls"); response.setContentType('application/msexcel') response.setHeader('content-disposition', "attachment;filename=${downname}") workbook.write(response.outputStream) response.outputStream.flush() }catch (Exception e){ println("导出Excel失败!"); e.printStackTrace(); }
批量导入的原理很简单:获取工作簿 ->获取工作表 ->循环行数据,逐个获取行数据 ->循环行数据,逐个获取单元格数据
批量导入下面三列数据:
代码实现:
//批量导入 def batchExcel(params) { def iStream = new FileInputStream("C:\\Users\\Administrator\\Desktop\\POI\\sample.xls"); def workbook = new HSSFWorkbook(iStream); def sheet = workbook.getSheetAt(0); if (sheet.getFirstRowNum() == sheet.getLastRowNum()) { print("批量导入失败!Excel文件的内容不能为空!。"); return false } def row, cellData = []; def dataList = []; for (int i = 2; i < sheet.getPhysicalNumberOfRows(); i++) { cellData = []; row = sheet.getRow(i); for (int j = 0; j < row.getPhysicalNumberOfCells(); j++) { cellData.add(getValue(row.getCell(j))); } dataList.add(cellData); } dataList.each { println(it) } }
效果:
批量导入的读取数据用 POI 实现十分简单,麻烦的是获取的值是带属性的,为了适配具体的业务,很多时候就要进行值的属性判断,属性改变等等,就是说大量的处理才能给你存数据库里,还有要做具体到单元格的导入报错信息提醒,毕竟存在本来这个单元格是要写数字的,结果给你写了一堆英文,导入存储失败,就要提醒到这个单元格填写内容错误了。
还有批量导入存表的时候,切记要加事务处理,否则第一次导入 100 条数据失败了,却还是存了 50 条,要不就一条不存,要不就存好 100 条数据。
poi 操作 word 文档,较于操作 Excel,功能则少了很多。写入或是读取,都是通过两种手段:
1,段落。
2,table。
写一个简单的 word 文档:
创建 Word 文件 ->新建一个段落(创建一个表格) ->设置段落样式 ->写入值 ->输出流输出 word。
代码示例:
//写word def write_word() { try { XWPFDocument document = new XWPFDocument();// 创建Word文件 XWPFParagraph p = document.createParagraph();// 新建一个段落 p.setAlignment(ParagraphAlignment.CENTER);// 设置段落的对齐方式 XWPFRun r = p.createRun();//创建段落文本 r.setText("---段落文本内容---");//设置文本内容 r.setBold(true);//设置为粗体 def table = document.createTable(3, 3);//创建一个表格 table.getRow(0).getCell(0).setText("小三"); table.getRow(0).getCell(1).setText("小玲"); table.getRow(0).getCell(2).setText("小7"); def downname = encodeFileName("导出word" + UtilTimeFormat.getFormat(new Date(), "yyyy-MM-dd") + ".docx") response.setContentType('application/vnd.ms-word.document.12'); response.setHeader('content-disposition', "attachment;filename=${downname}"); document.write(response.outputStream); response.outputStream.flush(); } catch (Exception e) { println("导出Word失败!"); e.printStackTrace(); } }
读一个 word 文档:
获取 Word 文件 ->获取段落(获取表格) ->获取段落文字(获取表格单元格文字)
获取如下 word 内容:
代码示例:
//读word def down_cqtz() { FileInputStream stream = new FileInputStream("C:\\Users\\Administrator\\Desktop\\POI\\poiForWord.docx"); //def getParagraph=document.paragraphs[0];//直接获取第一个段落 for (def p : document.getParagraphs())//遍历段落 { print(p.getParagraphText()) } // def table=document.tables[0];//直接获取第一张表格 for (def table : document.getTables())//遍历表格 { for (def row : table.getRows()) { for (def cell : row.getTableCells()) { print(cell.getText()) } } } }
效果:
由于 POI 对 word 的支持不够友好(可能是国内的 POI 的中文 api 确实少的缘故吧),建议使用对 word 操作的时候读写结合,在一个 word 模板上读写操作,这样的实现会好一点。
POI 不止对 Excel 和 Word 有操作支持,对 Microsoft Office 的几个办公软件套件都有支持,但国内用户基本没有,相关文档就更少了,在这里就不赘述了。
上面有介绍到如何读写简单的 Excel 和 Word,其实实际应用上功能需求远超过上面的这些,POI 对 Excel 和 Word 的各种属性样式支持也是挺丰富的(大多是 Excel),相关的API在网上也是有够用的,毕竟免费开源,用户量大,实在网上找不到功能的就得看源码找了。日后有空的时候,可以到网上找找,源码看看,整合一些样式设置,功能模块的。
本文首发于GitChat,未经授权不得转载,转载需与GitChat联系。
阅读全文: http://gitbook.cn/gitchat/activity/5b602df055c8d0781bbdb38c
您还可以下载 CSDN 旗下精品原创内容社区 GitChat App ,阅读更多 GitChat 专享技术内容哦。