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 的几个办公软件套件都有支持,但国内用户基本没有,相关文档就更少了,在这里就不赘述了。