Apache POI 简介
Apache POI 是用 Java 编写的免费开源的跨平台的 Java API,Apache POI 提供 API 给 Java 程式对 Microsoft Office(Excel、WORD、PowerPoint、Visio 等,主要实现用于 Excel)格式档案读和写的功能,POI 为 “ Poor Obfuscation Implementation ” 的首字母缩写,意为“简洁版的模糊实现”。
POI 技术优劣介绍
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 格式档案的功能。
其中,POI EXCEL 文档结构类:
HSSFWorkbook excel文档对象。
HSSFSheet excel的sheet HSSFRow excel的行。
HSSFCell excel的单元格 HSSFFont excel字体。
HSSFName 名称 HSSFDataFormat 日期格式。
HSSFHeader sheet头。
HSSFFooter sheet尾。
HSSFCellStyle cell样式。
HSSFDateUtil 日期。
HSSFPrintSetup 打印。
HSSFErrorConstants 错误信息表。
POI 导出 Excel
所用插件: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 的读写
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 的几个办公软件套件都有支持,但国内用户基本没有,相关文档就更少了,在这里就不赘述了。