且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

使用POI操作Excel的几点注意事项

更新时间:2022-08-28 11:10:48

首先说说现在我所知道的Java编辑Excel文件的两大开源工具:

jakarta POI和JavaExcelAPI(简称JXL),这两套工具我都试用了一这段时间,感觉各有优劣吧。POI在某些细节有些小Bug并且不支持写入图片,其他方面都挺不错的;

JXL就惨了,除了支持写入图片外,我暂时看不到它比POI好的地方,我碰到的主要的问题就是对公式支持不是很好,很多带有公式的Excel文件用JXL 打开后,公式就丢失了(比如now(),today()),在网上看到其他大虾评论说JXL写入公式也有问题,另外,JXL操作Excel文件的效率比 POI低一点。经过比较后,我选择了POI开发我的项目。

现在我要做的东西基本完成啦,我把这段时间使用POI的一些心得总结出来,希望能对和我遇到相同问题的朋友有所帮助,至于POI基本的使用方法,自己去看文档吧。
  1. 设置分页符的bug
    POI里的HSSFSheet类提供了setRowBreak方法可以设置Sheet的分页符。

    Bug:如果你要设置分页符的Sheet是本来就有的,并且你没有在里面插入过分页符,那么调用setRowBreak时POI会抛出空指针的异常。

    解决方法:在Excel里给这个sheet插入一个分页符,用POI打开后再把它删掉,然后你就可以随意插入分页符了。

    如果sheet是由POI生成的则没有这个问题。我跟踪了setRowBreak的源代码,发现是Sheet.Java下的 PageBreakRecord rowBreaks这个变量在搞鬼,如果Sheet里原来没有分页符,开发这个模块的那位兄台忘了为这个对象new实例,所以只能我们先手工给Excel 插入一个分页符来触发POI为rowBreaks创建实例。
  2. 如何拷贝行
    我在gmane.org的POI用户论坛翻遍了每个相关的帖子,找遍了api,也没看到一个拷贝行的方法,没办法,只能自己写:

Java代码 使用POI操作Excel的几点注意事项 使用POI操作Excel的几点注意事项使用POI操作Excel的几点注意事项
  1. //注:this.fWorkbook是一个HSSHWorkbook,请自行在外部new   
  2. public void copyRows(String pSourceSheetName,   
  3.         String pTargetSheetName,   
  4.         int pStartRow, int pEndRow,   
  5.         int pPosition) {   
  6.     HSSFRow sourceRow = null;   
  7.     HSSFRow targetRow = null;   
  8.     HSSFCell sourceCell = null;   
  9.     HSSFCell targetCell = null;   
  10.     HSSFSheet sourceSheet = null;   
  11.     HSSFSheet targetSheet = null;   
  12.     Region region = null;   
  13.     int cType;   
  14.     int i;   
  15.     short j;   
  16.     int targetRowFrom;   
  17.     int targetRowTo;   
  18.   
  19.     if ((pStartRow == -1) || (pEndRow == -1)) {   
  20.         return;   
  21.     }   
  22.     sourceSheet = this.fWorkbook.getSheet(pSourceSheetName);   
  23.     targetSheet = this.fWorkbook.getSheet(pTargetSheetName);   
  24.         //拷贝合并的单元格   
  25.     for (i = 0; i 
  26.         region = sourceSheet.getMergedRegionAt(i);   
  27.         if ((region.getRowFrom() >= pStartRow) && (region.getRowTo() 
  28.             targetRowFrom = region.getRowFrom() - pStartRow + pPosition;   
  29.             targetRowTo = region.getRowTo() - pStartRow + pPosition;   
  30.             region.setRowFrom(targetRowFrom);   
  31.             region.setRowTo(targetRowTo);   
  32.             targetSheet.addMergedRegion(region);   
  33.         }   
  34.     }   
  35.         //设置列宽   
  36.     for (i = pStartRow; i 
  37.         sourceRow = sourceSheet.getRow(i);   
  38.         if (sourceRow != null) {   
  39.             for (j = sourceRow.getFirstCellNum(); j 
  40.                 targetSheet.setColumnWidth(j, sourceSheet.getColumnWidth(j));   
  41.             }   
  42.             break;   
  43.         }   
  44.     }   
  45.         //拷贝行并填充数据   
  46.     for (; i 
  47.         sourceRow = sourceSheet.getRow(i);   
  48.         if (sourceRow == null) {   
  49.             continue;   
  50.         }   
  51.         targetRow = targetSheet.createRow(i - pStartRow + pPosition);   
  52.         targetRow.setHeight(sourceRow.getHeight());   
  53.         for (j = sourceRow.getFirstCellNum(); j 
  54.             sourceCell = sourceRow.getCell(j);   
  55.             if (sourceCell == null) {   
  56.                 continue;   
  57.             }   
  58.             targetCell = targetRow.createCell(j);   
  59.             targetCell.setEncoding(sourceCell.getEncoding());   
  60.             targetCell.setCellStyle(sourceCell.getCellStyle());   
  61.             cType = sourceCell.getCellType();   
  62.             targetCell.setCellType(cType);   
  63.             switch (cType) {   
  64.                 case HSSFCell.CELL_TYPE_BOOLEAN:   
  65.                     targetCell.setCellValue(sourceCell.getBooleanCellValue());   
  66.                     break;   
  67.                 case HSSFCell.CELL_TYPE_ERROR:   
  68.                     targetCell.setCellErrorValue(sourceCell.getErrorCellValue());   
  69.                     break;   
  70.                 case HSSFCell.CELL_TYPE_FORMULA:   
  71.                                         //parseFormula这个函数的用途在后面说明   
  72.                     targetCell.setCellFormula(parseFormula(sourceCell.getCellFormula()));   
  73.                     break;   
  74.                 case HSSFCell.CELL_TYPE_NUMERIC:   
  75.                     targetCell.setCellValue(sourceCell.getNumericCellValue());   
  76.                     break;   
  77.                 case HSSFCell.CELL_TYPE_STRING:   
  78.                     targetCell.setCellValue(sourceCell.getStringCellValue());   
  79.                     break;   
  80.             }   
  81.         }   
  82.     }   
  83. }  
这个函数有两个问题暂时无法解决:

a、只能在同一个Workbook里面使用,跨Workbook总是拷不过去,不知道为什么?

b、由于在拷贝行时也把行高也拷过去了,如果往这些单元格里写入的数据长度超过单元格长度,那么他们不会自动调整行高!

3、公式的问题

POI对Excel公式的支持是相当好的,但是我发现一个问题,如果公式里面的函数不带参数,比如now()或today(),那么你通过 getCellFormula()取出来的值就是now(ATTR(semiVolatile))和today(ATTR (semiVolatile)),这样的值写入Excel是会出错的,这也是我上面copyRow的函数在写入公式前要调用parseFormula的原因,parseFormula这个函数的功能很简单,就是把ATTR(semiVolatile)删掉,我把它的代码贴出来:
Java代码 使用POI操作Excel的几点注意事项 使用POI操作Excel的几点注意事项使用POI操作Excel的几点注意事项
  1. private String parseFormula(String pPOIFormula) {   
  2.     final String cstReplaceString = "ATTR(semiVolatile)"//$NON-NLS-1$   
  3.     StringBuffer result = null;   
  4.     int index;   
  5.   
  6.     result = new StringBuffer();   
  7.     index = pPOIFormula.indexOf(cstReplaceString);   
  8.     if (index >= 0) {   
  9.         result.append(pPOIFormula.substring(0, index));   
  10.         result.append(pPOIFormula.substring(index + cstReplaceString.length()));   
  11.     } else {   
  12.         result.append(pPOIFormula);   
  13.     }   
  14.   
  15.     return result.toString();   
  16. }  
至于为什么会出现ATTR(semiVolatile),还需要大家的探索精神!
Java代码 使用POI操作Excel的几点注意事项 使用POI操作Excel的几点注意事项使用POI操作Excel的几点注意事项
  1. public boolean drawImage(Image image, int dx1, int dy1, int dx2, int dy2,   
  2.                 int sx1, int sy1, int sx2, int sy2, Color bgColor, ImageObserver imageobserver) {   
  3.     if (logger.check(POILogger.WARN)) {   
  4.         logger.log(POILogger.WARN, "drawImage() not supported");   
  5.     }   
  6.     return true;   
  7. }  
所以我强烈建议大家,以后使用第三方开发包一定尽量下载它的源代码,这样你在碰到问题时,看看它的的内部是怎么实现的,很多时候就可以不必重蹈我的覆辙了。既然POI不能写入图片,那我们只能把目光投向JXL,我用JXL写入图片功能是实现了,付出的代价是now()和today()这些函数丢失掉了,鱼与熊掌不能兼得吧。 (T117)