java处理excel的2大框架:jakarta POI和JavaExcelAPI(简称JXL)

java当初把核心处理设成Unicode,带来的好处是另代码适应了多语言环境。然而由于老外的英语只有26个字母,有些情况下,一些程序员用8位的byte处理,一不小心就去掉了CJK的高位。或者是由于习惯在程序中采用硬编码,还有多种原因,使得许多java应用在CJK的处理上很烦恼。还好在POIHSSF中考虑到这个问题,可以设置encoding为双字节。

POI可以到www.apache.org下载到。编译好的jar主要有这样4个:poi包,poi Browser包,poihdf包,poi hssf例程包。实际运行时,需要有poi包就可以了。如果用Jakarta ant编译和运行,下载apacheJakarta POI的release中的src包,它里面已经为你生成好了build文件了。只要运行ant就可以了(ant的安装和使用在此不说了)。如果是用Jbuilder运行,请在新建的项目中加入poi包。以Jbuilder6为例,选择Tools菜单项的configlibraries...选项,新建一个lib。在弹出的菜单中选择poi包,如这个jakarta-poi-1.5.1-final-20020820.jar,把poi添加到jbuilder中。然后,右键点击你的项目,在project的properties菜单中path的required Libraries中,点add,添加刚才加入到jbuilder中的poi到你现在的项目中。如果你仅仅是为了熟悉POIhssf的使用,可以直接看POI的samples包中的源代码,并且运行它。hssf的各种对象都有例程的介绍。hssf提供的例程在org.apache.poi.hssf.usermodel.examples包中,共有14个,生成的目标xls都是workbook.xls。如果你想看更多的例程,可以参考hssf的Junittest cases,在poi的包的源代码中有。hssf都有测试代码。

这里只对部分例程的实现做介绍。

HSSF提供给用户使用的对象在org.apache.poi.hssf.usermodel包中,主要部分包括Excell对象,样式和格式,还有辅助操作。有以下几种对象:

HSSFWorkbook excell的文档对象

HSSFSheet excell的表单

HSSFRow excell的行

HSSFCell excell的格子单元

HSSFFont excell字体

HSSFName 名称

HSSFDataFormat 日期格式

在poi1.7中才有以下2项:

HSSFHeader sheet头

HSSFFooter sheet尾

和这个样式

HSSFCellStyle cell样式

辅助操作包括

HSSFDateUtil 日期

HSSFPrintSetup 打印

HSSFErrorConstants 错误信息表

仔细看org.apache.poi.hssf包的结构,不难发现HSSF的内部实现遵循的是MVC模型。

这里我用Rose把org.apache.poi.hssf.usermodel包中的对象反向导入并根据相互关系作了整理,详见下面两图:

图1 基本对象

从中不难可以发现每一个基本对象都关联了一个Record对象。Record对象是一个参考Office格式的相关记录。

图2 HSSFWorkbook

HSSFWorkbook即是一个Excell对象。这幅类图体现的是HSSFWorkbook和基本对象的相互关系。可见,许多对象中也建立了Workbook的引用。还需要注意的是在HSSFWorkbook和HSSFSheet中建立了log机制POILogger,而且POILogger也是使用apache Log4J实现的。

先看poi的examples包中提供的最简单的例子,建立一个空xls文件。

Java代码 收藏代码
  1. importorg.apache.poi.hssf.usermodel.HSSFWorkbook;
  2. importjava.io.FileOutputStream;
  3. importjava.io.IOException;
  4. public classNewWorkbook
  5. {
  6. public static voidmain(String[] args)
  7. throws IOException
  8. {
  9. HSSFWorkbook wb = newHSSFWorkbook();//建立新HSSFWorkbook对象
  10. FileOutputStream fileOut = newFileOutputStream("workbook.xls");
  11. wb.write(fileOut);//把Workbook对象输出到文件workbook.xls中
  12. fileOut.close();
  13. }
  14. }
  15. 通过这个例子,我们建立的是一个空白的xls文件(不是空文件)。在此基础上,我们可以进一步看其它的例子。
  16. importorg.apache.poi.hssf.usermodel.*;
  17. importjava.io.FileOutputStream;
  18. importjava.io.IOException;
  19. public classCreateCells
  20. {
  21. public static voidmain(String[] args)
  22. throws IOException
  23. {
  24. HSSFWorkbook wb = newHSSFWorkbook();//建立新HSSFWorkbook对象
  25. HSSFSheet sheet = wb.createSheet("newsheet");//建立新的sheet对象
  26. // Create a row and put some cells in it. Rows are0 based.
  27. HSSFRow row =sheet.createRow((short)0);//建立新行
  28. // Create a cell and put a value init.
  29. HSSFCell cell =row.createCell((short)0);//建立新cell
  30. cell.setCellValue(1);//设置cell的整数类型的值
  31. // Or do it on one line.
  32. row.createCell((short)1).setCellValue(1.2);//设置cell浮点类型的值
  33. row.createCell((short)2).setCellValue("test");//设置cell字符类型的值
  34. row.createCell((short)3).setCellValue(true);//设置cell布尔类型的值
  35. HSSFCellStyle cellStyle =wb.createCellStyle();//建立新的cell样式
  36. cellStyle.setDataFormat(HSSFDataFormat.getFormat("m/d/yyh:mm"));//设置cell样式为定制的日期格式
  37. HSSFCell dCell=row.createCell((short)4);
  38. dCell.setCellValue(newDate());//设置cell为日期类型的值
  39. dCell.setCellStyle(cellStyle);//设置该cell日期的显示格式
  40. HSSFCell csCell=row.createCell((short)5);
  41. csCell.setEncoding(HSSFCell.ENCODING_UTF_16);//设置cell编码解决中文高位字节截断
  42. csCell.setCellValue("中文测试_Chinese WordsTest");//设置中西文结合字符串
  43. row.createCell((short)6).setCellType(HSSFCell.CELL_TYPE_ERROR);//建立错误cell
  44. // Write the output to a file
  45. FileOutputStream fileOut = newFileOutputStream("workbook.xls");
  46. wb.write(fileOut);
  47. fileOut.close();
  48. }
  49. }
  50. 我稍微修改了原来的examples包中的CreateCells类写了上面的功能测试类。通过这个例子,我们可以清楚的看到xls文件从大到小包括了HSSFWorkbookHSSFSheet HSSFRowHSSFCell这样几个对象。我们可以在cell中设置各种类型的值。尤其要注意的是如果你想正确的显示非欧美的字符时,尤其象中日韩这样的语言,必须设置编码为16位的即是HSSFCell.ENCODING_UTF_16,才能保证字符的高8位不被截断而引起编码失真形成乱码。
  51. 其他测试可以通过参考examples包中的测试例子掌握poi的详细用法,包括字体的设置,cell大小和低纹的设置等。需要注意的是POI是一个仍然在完善中的公开代码的项目,所以有些功能正在不断的扩充。如HSSFSheet的getFooter()getHeader()和setFooter(HSSFFooter hsf) setHeader(HSSFHeaderhsh)是在POI1.7中才有的,而POI1.5中就没有。运行测试熟悉代码或者使用它做项目时请注意POI的版本。
  52. 另外需要注意的是HSSF也有它的对xls基于事件的解析。可以参考例程中的EventExample.java。它通过实现HSSFListener完 成从普通流认知Xls中包含的内容,在apacheCocoon中的org.apache.cocoon.serialization.HSSFSerializer中用到了这个解析。因为Cocoon2是基于事件的, 所以POI为了提供快速的解析也提供了相应的事件。当然我们自己也可以实现这个事件接口。
  53. 因为POI还不是一个足够成熟的项目,所以有必要做进一步的开发和测试。但是它已经为我们用纯java操作ole2对象提供了可能,而且克服了ole对象调用的缺陷,提供了服务器端的Excel解决方案。
  54. ======================================================
  55. 利用Java 创建和读取Excel文档
  56. 为了保证示例程序的运行,必须安装Java 2sdk1.4.0 和Jakarta POI,JakartaPOI的Web站点是:http://jakarta.apache.org/poi/
  57. 示例1将演示如何利用Jakarta POI API 创建Excel文档。
  58. 示例1程序如下:
  59. importorg.apache.poi.hssf.usermodel.HSSFWorkbook;
  60. importorg.apache.poi.hssf.usermodel.HSSFSheet;
  61. importorg.apache.poi.hssf.usermodel.HSSFRow;
  62. importorg.apache.poi.hssf.usermodel.HSSFCell;
  63. importjava.io.FileOutputStream;
  64. public class CreateXL{
  65. public static StringoutputFile="D:/JTest/gongye.xls";
  66. public static voidmain(String argv[])
  67. {
  68. try
  69. {
  70. // 创建新的Excel 工作簿
  71. HSSFWorkbook workbook = newHSSFWorkbook();
  72. // 在Excel工作簿中建一工作表,其名为缺省值
  73. // 如要新建一名为"效益指标"的工作表,其语句为:
  74. // HSSFSheet sheet =workbook.createSheet("效益指标");
  75. HSSFSheet sheet = workbook.createSheet();
  76. // 在索引0的位置创建行(最顶端的行)
  77. HSSFRow row =sheet.createRow((short)0);
  78. //在索引0的位置创建单元格(左上端)
  79. HSSFCell cell =row.createCell((short)0);
  80. // 定义单元格为字符串类型
  81. cell.setCellType(HSSFCell.CELL_TYPE_STRING);
  82. // 在单元格中输入一些内容
  83. cell.setCellValue("增加值");
  84. // 新建一输出文件流
  85. FileOutputStream fOut = newFileOutputStream(outputFile);
  86. // 把相应的Excel 工作簿存盘
  87. workbook.write(fOut);
  88. fOut.flush();
  89. // 操作结束,关闭文件
  90. fOut.close();
  91. System.out.println("文件生成...");
  92.  }catch(Exception e){
  93. System.out.println("已运行 xlCreate() : "+ e );
  94. }
  95. }
  96. }
  97. 读取Excel文档中的数据
  98. 示例2将演示如何读取Excel文档中的数据。假定在D盘JTest目录下有一个文件名为gongye.xls的Excel文件。
  99. 示例2程序如下:
  100. importorg.apache.poi.hssf.usermodel.HSSFWorkbook;
  101. importorg.apache.poi.hssf.usermodel.HSSFSheet;
  102. importorg.apache.poi.hssf.usermodel.HSSFRow;
  103. importorg.apache.poi.hssf.usermodel.HSSFCell;
  104. importjava.io.FileInputStream;
  105. public class ReadXL{
  106. public static StringfileToBeRead="D:/JTest/gongye.xls";
  107. public static voidmain(String argv[]){
  108. try{
  109. // 创建对Excel工作簿文件的引用
  110. HSSFWorkbook workbook = newHSSFWorkbook(newFileInputStream(fileToBeRead));
  111. // 创建对工作表的引用。
  112. //本例是按名引用(让我们假定那张表有着缺省名"Sheet1")
  113. HSSFSheet sheet =workbook.getSheet("Sheet1");
  114. // 也可用getSheetAt(intindex)按索引引用,
  115. // 在Excel文档中,第一张工作表的缺省索引是0,
  116. // 其语句为:HSSFSheet sheet =workbook.getSheetAt(0);
  117. // 读取左上端单元
  118. HSSFRow row =sheet.getRow(0);
  119. HSSFCell cell =row.getCell((short)0);
  120. //输出单元内容,cell.getStringCellValue()就是取所在单元的值
  121. System.out.println("左上端单元是: " +cell.getStringCellValue());
  122. }catch(Exception e){
  123. System.out.println("已运行xlRead() : " +e );
  124. }
  125. }
  126. }
  127. 设置单元格格式
  128. 在这里,我们将只介绍一些和格式设置有关的语句,我们假定workbook就是对一个工作簿的引用。在Java
  129. 中,第一步要做的就是创建和设置字体和单元格的格式,然后再应用这些格式:
  130.   1、创建字体,设置其为红色、粗体:
  131. HSSFFont font = workbook.createFont();
  132. font.setColor(HSSFFont.COLOR_RED);
  133. font.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD);
  134. 2、创建格式
  135. HSSFCellStyle cellStyle=workbook.createCellStyle();
  136. cellStyle.setFont(font);
  137. 3、应用格式
  138. HSSFCell cell =row.createCell((short)0);
  139. cell.setCellStyle(cellStyle);
  140. cell.setCellType(HSSFCell.CELL_TYPE_STRING);
  141. cell.setCellValue("标题");
  142.   总之,如本篇文章所演示的一样,Java程序员不必担心Excel工作表中的数据了,利用Jakarta POIAPI,
  143. 我们就可以轻易的在程序中存取Excel文档。
  144. ==============================================
  145. 首先说说现在我所知道的Java编辑Excel文件的两大开源工具:
  146. jakartaPOI和JavaExcelAPI(简称JXL),这两套工具我都试用了一这段时间,感觉各有优劣吧。POI在某些细节有些小Bug并且不支持写入图片,其他方面都挺不错的;
  147. JXL就惨了,除了支持写入图片外,我暂时看不到它比POI好的地方,我碰到的主要的问题就是对公式支持不是很好,很多带有公式的Excel文件用JXL打开后,公式就丢失了(比如now(),today()),在网上看到其他大虾评论说JXL写入公式也有问题,另外,JXL操作Excel文件的效率比POI低一点。经过比较后,我选择了POI开发我的项目。
  148. 现在我要做的东西基本完成啦,我把这段时间使用POI的一些心得总结出来,希望能对和我遇到相同问题的朋友有所帮助,至于POI基本的使用方法,自己去看文档吧。
  149. 1、设置分页符的bug
  150. POI里的HSSFSheet类提供了setRowBreak方法可以设置Sheet的分页符。
  151. Bug:如果你要设置分页符的Sheet是本来就有的,并且你没有在里面插入过分页符,那么调用setRowBreak时POI会抛出空指针的异常。
  152. 解决方法:在Excel里给这个sheet插入一个分页符,用POI打开后再把它删掉,然后你就可以随意插入分页符了。
  153. 如果sheet是由POI生成的则没有这个问题。我跟踪了setRowBreak的源代码,发现是Sheet.Java下的PageBreakRecordrowBreaks这个变量在搞鬼,如果Sheet里原来没有分页符,开发这个模块的那位兄台忘了为这个对象new实例,所以只能我们先手工给Excel插入一个分页符来触发POI为rowBreaks创建实例。
  154. 2、如何拷贝行
  155. 我在gmane.org的POI用户论坛翻遍了每个相关的帖子,找遍了api,也没看到一个拷贝行的方法,没办法,只能自己写:
  156. //注:this.fWorkbook是一个HSSHWorkbook,请自行在外部new
  157. public voidcopyRows
  158. (String pSourceSheetName,
  159. String pTargetSheetName,
  160. int pStartRow, intpEndRow,
  161. int pPosition)
  162. {
  163. HSSFRow sourceRow =null;
  164. HSSFRow targetRow =null;
  165. HSSFCell sourceCell =null;
  166. HSSFCell targetCell =null;
  167. HSSFSheet sourceSheet =null;
  168. HSSFSheet targetSheet =null;
  169. Region region =null;
  170. int cType;
  171. int i;
  172. short j;
  173. int targetRowFrom;
  174. int targetRowTo;
  175. if ((pStartRow ==-1) || (pEndRow ==-1))
  176. {
  177. return;
  178. }
  179. sourceSheet =this.fWorkbook.getSheet(pSourceSheetName);
  180. targetSheet =this.fWorkbook.getSheet(pTargetSheetName);
  181. //拷贝合并的单元格
  182. for (i = 0;i < sourceSheet.getNumMergedRegions();i++)
  183. {
  184. region = sourceSheet.getMergedRegionAt(i);
  185. if ((region.getRowFrom()>= pStartRow) &&(region.getRowTo() <= pEndRow))
  186. {
  187. targetRowFrom = region.getRowFrom() - pStartRow +pPosition;
  188. targetRowTo = region.getRowTo() - pStartRow +pPosition;
  189. region.setRowFrom(targetRowFrom);
  190. region.setRowTo(targetRowTo);
  191. targetSheet.addMergedRegion(region);
  192. }
  193. }
  194. //设置列宽
  195. for (i = pStartRow; i<= pEndRow; i++)
  196. {
  197. sourceRow = sourceSheet.getRow(i);
  198. if (sourceRow !=null)
  199. {
  200. for (j = sourceRow.getFirstCellNum();j < sourceRow.getLastCellNum();j++)
  201. {
  202. targetSheet.setColumnWidth(j,sourceSheet.getColumnWidth(j));
  203. }
  204. break;
  205. }
  206. }
  207. //拷贝行并填充数据
  208. for (;i <= pEndRow;i++)
  209. {
  210. sourceRow = sourceSheet.getRow(i);
  211. if (sourceRow ==null)
  212. {
  213. continue;
  214. }
  215. targetRow = targetSheet.createRow(i - pStartRow +pPosition);
  216. targetRow.setHeight(sourceRow.getHeight());
  217. for (j = sourceRow.getFirstCellNum();j < sourceRow.getLastCellNum();j++)
  218. {
  219. sourceCell = sourceRow.getCell(j);
  220. if (sourceCell ==null)
  221. {
  222. continue;
  223. }
  224. targetCell = targetRow.createCell(j);
  225. targetCell.setEncoding(sourceCell.getEncoding());
  226. targetCell.setCellStyle(sourceCell.getCellStyle());
  227. cType = sourceCell.getCellType();
  228. targetCell.setCellType(cType);
  229. switch (cType)
  230. {
  231. caseHSSFCell.CELL_TYPE_BOOLEAN:
  232. targetCell.setCellValue(sourceCell.getBooleanCellValue());
  233. break;
  234. caseHSSFCell.CELL_TYPE_ERROR:
  235. targetCell.setCellErrorValue(sourceCell.getErrorCellValue());
  236. break;
  237. caseHSSFCell.CELL_TYPE_FORMULA:
  238. //parseFormula这个函数的用途在后面说明
  239. targetCell.setCellFormula(parseFormula(sourceCell.getCellFormula()));
  240. break;
  241. caseHSSFCell.CELL_TYPE_NUMERIC:
  242. targetCell.setCellValue(sourceCell.getNumericCellValue());
  243. break;
  244. caseHSSFCell.CELL_TYPE_STRING:
  245. targetCell.setCellValue(sourceCell.getStringCellValue());
  246. break;
  247. }
  248. }
  249. }
  250. }
  251. 这个函数有两个问题暂时无法解决:
  252. a、只能在同一个Workbook里面使用,跨Workbook总是拷不过去,不知道为什么?
  253. b、由于在拷贝行时也把行高也拷过去了,如果往这些单元格里写入的数据长度超过单元格长度,那么他们不会自动调整行高!
  254. 3、公式的问题
  255. POI对Excel公式的支持是相当好的,但是我发现一个问题,如果公式里面的函数不带参数,比如now()或today(),那么你通过getCellFormula()取出来的值就是now(ATTR(semiVolatile))和today(ATTR(semiVolatile)),这样的值写入Excel是会出错的,这也是我上面copyRow的函数在写入公式前要调用parseFormula的原因,parseFormula这个函数的功能很简单,就是把ATTR(semiVolatile)删掉,我把它的代码贴出来:
  256. private String parseFormula(StringpPOIFormula)
  257. {
  258. final String cstReplaceString ="ATTR(semiVolatile)";//$NON-NLS-1$
  259. StringBuffer result =null;
  260. int index;
  261. result = newStringBuffer();
  262. index =pPOIFormula.indexOf(cstReplaceString);
  263. if (index >=0)
  264. {
  265. result.append(pPOIFormula.substring(0,index));
  266. result.append(pPOIFormula.substring(index +cstReplaceString.length()));
  267. }
  268. else
  269. {
  270. result.append(pPOIFormula);
  271. }
  272. returnresult.toString();
  273. }
  274. 至于为什么会出现ATTR(semiVolatile),还需要大家的探索精神!
  275. 4、向Excel写入图片的问题。
  276. 我上POI论坛查相关帖子,得到两种结论:
  277. 1、不支持写入图片;
  278. 2、支持写入图片,通过EscherGraphics2d这个Class实现。于是我就去查EscherGraphics2d这个Class,发现这个Class提供了N个drawImage方法,喜出望外的我开始写代码,结果调了一天,一直看不到效果,黔驴技穷的我在万般无奈下只好跟踪进drawImage这个函数内部,经过N个函数调用后在最底层函数发现了最终答案:
  279. public booleandrawImage(Image image, intdx1, int dy1,int dx2, intdy2, int sx1,int sy1,
  280. int sx2, intsy2, Color bgColor, ImageObserverimageobserver)
  281. {
  282. if (logger.check( POILogger.WARN))
  283. logger.log(POILogger.WARN,"drawImage() notsupported");
  284. returntrue;
  285. }
  286. 所以我强烈建议大家,以后使用第三方开发包一定尽量下载它的源代码,这样你在碰到问题时,看看它的的内部是怎么实现的,很多时候就可以不必重蹈我的覆辙了。既然POI不能写入图片,那我们只能把目光投向JXL,我用JXL写入图片功能是实现了,付出的代价是now()和today()这些函数丢失掉了,鱼与熊掌不能兼得吧。

你可能感兴趣的:(java)