本文將闡述如何用POI來讀取/寫入完整的Excel文件。
約定:POI項目2.0版現在已經接近正式發行階段,開發進度迅速,不斷有新的功能集成到原有的系統,同時也有對原有系統的修改。
為了保證本文的及時性,本文將按照最近的1.9開發版說明。雖然編譯最近的發行版源代碼也能正常運行,但現在的代碼和2.0的發行版會有一些出入。
一、Excel基礎
Microsoft Excel 97文件格式也被稱為BIFF8,最近版本的Excel只對該格式作了少量的改動。增加對新格式的支持除了增加項目的復雜性之外,唯一的效果也許只是不得不使每個用戶升級代碼,沒有什麼實際的好處。
因此,在下文說明中,凡是提到Excel 97格式的地方其實都是指Excel從97到XP的格式。
二、HSSF概況
POI項目實現的Excel 97文件格式稱為HSSF??也許你已經猜到,HSSF是Horrible SpreadSheet Format的縮寫,也即“討厭的電子表格格式”(微軟使某些原本簡單的事情過分復雜,同時又過分簡單地處理了某些原本需要靈活性的事情,讓人不勝佩服!)
也許HSSF的名字有點滑稽,就本質而言它是一個非常嚴肅、正規的API。通過HSSF,你可以用純Java代碼來讀取、寫入、修改Excel文件。
前面一篇文章提到了POIFS,那麼HSSF和POIFS又有什麼關系呢?就象其他POI的API一樣,HSSF建立在POIFS的基礎上,因此在HSSF內的有些代碼和前文的某些代碼很相似。不過,當我們編寫基於HSSF API的代碼時,一般不需要了解POIFS API的細節。
HSSF為讀取操作提供了兩類API:usermodel和eventusermodel,即“用戶模型”和“事件-用戶模型”。前者很好理解,後者比較抽象,但操作效率要高得多。usermodel主要有org.apache.poi.hssf.usermodel和org.apache.poi.hssf.eventusermodel包實現(在HSSF的早期版本中,org.apache.poi.hssf.eventusermodel屬於eventmodel包)。
usermodel包把Excel文件映射成我們熟悉的結構,諸如Workbook、Sheet、Row、Cell等,它把整個結構以一組對象的形式保存在內存之中。eventusermodel要求用戶熟悉文件格式的底層結構,它的操作風格類似於XML的SAX API和AWT的事件模型(這就是eventusermodel名稱的起源),要掌握竅門才能用好。
另外,eventusermodel的API只提供讀取文件的功能,也就是說不能用這個API來修改文件。
三、通過usermodel讀取文件
用HSSF的usermodel讀取文件很簡單。首先創建一個InputStream,然後創建一個HSSFWorkbook:
InputStream myxls = new FileInputStream("workbook.xls"));
HSSFWorkbook wb = new HSSFWorkbook(myxls);
有了HSSFWorkbook實例,接下來就可以提取工作表、工作表的行和列,例如:
HSSFSheet sheet = wb.getSheetAt(0); // 第一個工作表
HSSFRow row = sheet.getRow(2); // 第三行
HSSFCell cell = row.getCell((short)3); // 第四個單元格
上面這段代碼提取出第一個工作表第三行第四單元格。利用單元格對象可以獲得它的值,提取單元格的值時請注意它的類型:
if (cell.getCellType() == HSSFCell.CELL_TYPE_STRING) {
("單元格是字符串,值是: " + cell.getStringCellValue());
} else if (cell.getCellType() == HSSFCell.CELL_TYPE_NUMERIC) {
("單元格是數字,值是: " + cell.getCellValue());
} else () {
("單元格的值不是字符串或數值。");
}
如果搞錯了數據類型,程序將遇到異常。特別地,用HSSF處理日期數據要小心。Excel內部以數值的形式保存日期數據,區別日期數據的唯一辦法是通過單元格的格式(如果你曾經在Excel中設置過日期格式,應該明白這是什麼意思)。
因此,對於包含日期數據的單元格,cell.getCellType()將返回HSSFCell.CELL_TYPE_NUMERIC,不過利用工具函數HSSFDateUtil.isCellDateFormatted(cell)可以判斷出單元格的值是否為日期。isCellDateFormatted函數通過比較單元格的日期和Excel的內置日期格式得出結論??可以想象,按照這種判斷方法,很多時候isCellDateFormatted函數會返回否定的結論,存在一定的誤判可能。
本文附錄包含了一個在Servlet環境中利用HSSF創建和返回Excel工作簿的實例。
四、通過usermodel寫入文件
寫入XLS文件比讀取XLS文件還要簡單。創建一個HSSFWorkbook實例,然後在適當的時候創建一個把文件寫入磁盤的OutputStream,但延遲到處理結束時創建OutputStream也可以:
HSSFWorkbook wb = new HSSFWorkbook();
FileOutputStream fileOut
= new FileOutputStream("workbook.xls");
wb.write(fileOut);
fileOut.close();
創建工作表及其內容必須從相應的父對象出發,例如:
HSSFSheet sheet = wb.createSheet();
HSSFRow row = sheet.createRow((short)0);
HSSFCell cell = row.createCell((short)0);
cell.setCellValue(1);
row.createCell((short)1).setCellValue(1.2);
row.createCell((short)2).setCellValue("一個字符串");
row.createCell((short)3).setCellValue(true);
如果要設置單元格的樣式,首先要創建一個樣式對象,然後把它指定給一個單元格??或者把它指定給多個具有相同樣式的單元格,例如,如果Excel表格中有一個摘要行,摘要行的數據必須是粗體、斜體,你可以創建一個summaryRowStyle樣式對象,然後把這個樣式指定給所有摘要行上的單元格。
注意,CellFormat和CellStyle對象是工作簿對象的成員,單元格對象只是引用它們。
...
HSSFCellStyle style = workbook.createCellStyle();
style.setDataFormat
(HSSFDataFormat.getBuiltinFormat("($#,##0_);[Red]($#,##0)"));
style.setFillBackgroundColor(HSSFColor.AQUA.index);
style.setFillPattern(HSSFCellStyle.BIG_SPOTS);
...
someCell.setCellStyle(style);
someOtherCell.setCellStyle(style);
版本較新的HSSF允許使用數量有限的Excel公式。這一功能目前還是“Beta級質量”,正式使用之前務必仔細測試。指定公式的方式類如:someCell.setCellFormula(SUM(A1:A2:);。
當前,公式中已經可以調用所有內建的函數或操作符,但邏輯操作符和函數(例如IF函數)除外,這部分功能目前還在開發之中。