Apache POI For Java Excel<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
POI的主頁:http://jakarta.apache.org/poi
POI HSSF的Quick Guide,教初學者如何快速上手使用POI HSSF:
http://jakarta.apache.org/poi/hssf/quick-guide.html
筆者據使用經驗以爲:POI HSSF是當今市面上最強大的處理EXCEL表格的java工具,比韓國人寫的那個JExcelApi或其它幾種工具都要好。而且它是Apache的開源項目。當然POI HSSF也有缺點:不能直接支持EXCEL圖表,API文檔粗糙簡略,有些類和方法需要引用Apache項目中的其它一些包,包與包之間依賴關係比較複雜等等。
本文內容不在於提供一個POI HSSF 的完整使用指南(上面那個Apache主頁上的Quick Guide已經非常詳細),而是列出一些筆者在項目開發過程中找到的一些技巧、經驗。
目前POI版本爲2.5.1,org.apache.poi.hssf.usermodel包裏有一個HSSFChart類,裏面只有一個空方法createBarChart(),表明POI還不直接支持EXCEL圖表。
替代方法還是有的:因爲EXCEL圖表的源數據引用自EXCEL單元格。我們可以先新建一個EXCEL工作薄,作爲模板,在裏面創建圖表,指定它引用工作表中的一些特定單元格。然後我們用POI來讀取這個工作薄,把數據寫入到那些特定單元格。
首先要在模板裏創建可以動態引用單元格的“名稱”,利用“名稱”來創建圖表中的“系列”。
一、打開模板PoiTest.xls,點擊[插入]>[名稱]>[定義],創建四個“名稱”sx,s1y,s2y,s3y:
sx=OFFSET(Sheet1!$A$17,0,2,1,COUNTA(Sheet1!$17:$17)-4)
s1y=OFFSET(Sheet1!$A$18,0,2,1,COUNTA(Sheet1!$18:$18)-4)
s2y=OFFSET(Sheet1!$A$19,0,2,1,COUNTA(Sheet1!$19:$19)-3)
s3y=OFFSET(Sheet1!$A$20,0,2,1,COUNTA(Sheet1!$20:$20)-3)
這裏用到了兩個EXCEL函數,OFFSET()和COUNTA()函數。
其中COUNTA()可以返回一行或一列的單元格總數:
比如COUNTA(Sheet1!$A:$A),計算工作表Sheet1的A列的單元格數目。
又比如COUNTA(Sheet1!$17:$17),計算的是Sheet1的第17行的單元格數目。
當我們沒有在單元格里鍵入數據時,該單元格是不會被COUNTA()計算的。
OFFSET()函數用來引用一系列連續的單元格,它共有五個參數:
參數一,作爲位置參照的單元格。
參數二,行的起始偏移量(以參數一爲參照)。
參數三,列的起始偏移量(以參數一爲參照)。
參數四,跨行數。
參數五,跨列數。
比如:
OFFSET(Sheet1!$A:$1,1,2,3,4),表示引用範圍爲:C2:F4。
二、在模板中創建圖表,在圖表上點右鍵,選擇[源數據]>[系列],如圖建立三個系列:
點[添加]創建新的系列:
[名稱]表示系列名,可以直接輸入字串,也可以引用EXCEL單元格。
在[值]中輸入我們在上一步中創建的“名稱”,格式爲:模板名.xls!名稱。
在[分類(X)軸標誌(T)]中輸入我們在上一步中創建的“名稱”sx,格式爲:模板名.xls!名稱。它表示圖表區域的X軸將要顯示的內容。
三、用POI把數據寫入到相應的單元格中,圖表將會自動顯示對應的信息。
注意:
上面這種方法適用數據集合行數固定而列數動態變化的情況。
對於行數也動態變化的情況,只能先在模板裏預設儘可能多的“名稱”和“系列”。
對於行數和列數都固定的情形,沒必要這麼複雜,只要在圖表的[源數據]裏設置[數據區域],使之引用EXCEL模板中的一定範圍,如下圖:
設置單元格樣式
HSSFCellStyle類代表一種單元格樣式。可以通過這個類來設置單元格的邊框樣式、背景顏色、字體、水平和垂直對齊方式等等。
HSSFCellStyle titleStyle = workbook.createCellStyle(); titleStyle.setBorderBottom(HSSFCellStyle.BORDER_DOUBLE); titleStyle.setBorderLeft((short)1); titleStyle.setBorderRight((short)1); titleStyle.setBorderTop(HSSFCellStyle.BORDER_DOUBLE); titleStyle.setFillForegroundColor(HSSFColor.LIGHT_ORANGE.index); titleStyle.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND); |
注意:如果我們定義了一種樣式,把它賦給一些單元格。然後基於新的需要,更改該樣式中的某個屬性,再賦給另一些單元格。那麼之前單元格樣式的該屬性也會被同時更改。
比如我們定義了樣式,設置單元格背景色爲紅色:
HSSFCellStyle cellStyle = workbook.createCellStyle();
cellStyle.setFillForegroundColor(HSSFColor.RED.index); cellStyle.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND); |
然後把它賦給一個單元格:
HSSFCell cell1 = row.createCell((short)1);
cell1.setCellStyle(cellStyle); |
然後更改樣式中的背景色屬性爲藍色:
cellStyle.setFillForegroundColor(HSSFColor.BLUE.index); |
然後賦給另一個單元格:
HSSFCell cell2 = row.createCell((short)2);
cell2.setCellStyle(cellStyle); |
想當然,我們預計在最終結果中cell1的背景色爲紅色,cell2的背景色爲藍色。但是結果是:兩個單元格的背景色都變成了藍色。
遇到這種情況,要預先定義兩種不同的單元格樣式。
當一個EXCEL文件同時需要很多大同小異的單元格樣式時,這樣一一定義很麻煩。POI HSSF提供了一個HSSFCellUtil類(在org.apache.poi.hssf.usermodel.contrib包),裏面有幾個方法可以繞過HSSFCellStyle直接設定單元格的樣式,但這幾個方法會拋出NestableException異常,要處理這個異常,需要引用Apache的幾個Common包:
commons-beanutils.jar
commons-beanutils-bean-collections.jar
commons-beanutils-core.jar
commons-lang.jar
commons-logging-api.jar
合併單元格
HSSFSheet.addMergedRegion(new Region())方法可以合併單元格,Region()中的一個構造函數含有四個參數,分別代表起始行、起始列、結束行、結束列:
sheet.addMergedRegion(new Region(initRow, (short)(initCol-2), initRow + lists.size() - 1, (short)(initCol-2))); |
處理公式
HSSFCell.setCellFormula()方法用來在EXCEL單元格中寫入公式。
cell = row.createCell((short)(dataFlag)); cell.setCellType(HSSFCell.CELL_TYPE_FORMULA); cell.setCellFormula("SUM(" + getColLetter(initCol) + (listFlag+1) + ":" + getColLetter(dataFlag-1) + (listFlag+1) + ")"); cell.setCellStyle(nameStyle); |
處理鏈接
在POI中往單元格中寫鏈接,是用HYPERLINK函數搞定的。
HYPERLINK函數包含兩個參數,第一個參數是指向的URL地址,第二個參數是顯示字串。
爲了使鏈接效果更好,我們可以給鏈接所在單元格定義一種樣式,使鏈接顯示爲有下劃線的藍色字串:
HSSFCellStyle linkStyle = workbook.createCellStyle(); linkStyle.setBorderBottom((short)1); linkStyle.setBorderLeft((short)1); linkStyle.setBorderRight((short)1); linkStyle.setBorderTop((short)1); linkStyle.setFillForegroundColor(HSSFColor.SKY_BLUE.index); linkStyle.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND); HSSFFont font = workbook.createFont(); font.setFontName(HSSFFont.FONT_ARIAL); font.setUnderline((byte)1); font.setColor(HSSFColor.BLUE.index); linkStyle.setFont(font); |
中文處理:
要在通過POI生成的EXCEL中正常顯示中文,需要爲單元格設置編碼:
cell.setEncoding(HSSFCell.ENCODING_UTF_16); cell.setCellValue("部門"); |
完整的PoiServlet類:
package org.eleaf.poi.servlets;
import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List;
import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
import org.apache.poi.hssf.usermodel.HSSFCell; import org.apache.poi.hssf.usermodel.HSSFCellStyle; import org.apache.poi.hssf.util.HSSFColor; import org.apache.poi.hssf.util.Region; import org.apache.poi.hssf.usermodel.HSSFFont; import org.apache.poi.hssf.usermodel.HSSFRow; import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFWorkbook;
public class PoiServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("application/vnd.ms-excel;charset=utf-8"); response.setHeader("Content-Disposition", "attachment;filename=PoiTest.xls"); ServletOutputStream sos = response.getOutputStream(); HSSFWorkbook workbook = new HSSFWorkbook(getServletContext().getResourceAsStream("/PoiTest.xls")); HashMap map = getDatas(); workbook = writeDatas(workbook, map); workbook.write(sos); sos.close(); } /** * 將數據寫入到EXCEL中。 * @param workbook * @param map 數據集合 * @return */ private HSSFWorkbook writeDatas(HSSFWorkbook workbook, HashMap map) { HSSFCellStyle titleStyle = workbook.createCellStyle(); titleStyle.setBorderBottom(HSSFCellStyle.BORDER_DOUBLE); titleStyle.setBorderLeft((short)1); titleStyle.setBorderRight((short)1); titleStyle.setBorderTop(HSSFCellStyle.BORDER_DOUBLE); titleStyle.setFillForegroundColor(HSSFColor.LIGHT_ORANGE.index); titleStyle.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND); HSSFCellStyle dataStyle = workbook.createCellStyle(); dataStyle.setBorderBottom((short)1); dataStyle.setBorderLeft((short)1); dataStyle.setBorderRight((short)1); dataStyle.setBorderTop((short)1); dataStyle.setFillForegroundColor(HSSFColor.LIGHT_GREEN.index); dataStyle.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND); dataStyle.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER); HSSFCellStyle nameStyle = workbook.createCellStyle(); nameStyle.setBorderBottom((short)1); nameStyle.setBorderLeft((short)1); nameStyle.setBorderRight((short)1); nameStyle.setBorderTop((short)1); nameStyle.setFillForegroundColor(HSSFColor.LIGHT_CORNFLOWER_BLUE.index); nameStyle.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND); HSSFCellStyle linkStyle = workbook.createCellStyle(); linkStyle.setBorderBottom((short)1); linkStyle.setBorderLeft((short)1); linkStyle.setBorderRight((short)1); linkStyle.setBorderTop((short)1); linkStyle.setFillForegroundColor(HSSFColor.SKY_BLUE.index); linkStyle.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND); HSSFFont font = workbook.createFont(); font.setFontName(HSSFFont.FONT_ARIAL); font.setUnderline((byte)1); font.setColor(HSSFColor.BLUE.index); linkStyle.setFont(font); HSSFSheet sheet = workbook.getSheetAt(0); final int initRow = 17; final int initCol = 2; HSSFRow rTitle = sheet.createRow(initRow - 1); List lists = (List) map.get("list"); List titles = (List)map.get("title"); int titleFlag = initCol; for (Iterator it = titles.iterator(); it.hasNext();) { String title = (String)it.next(); HSSFCell cell = rTitle.createCell((short)titleFlag); cell.setCellStyle(titleStyle); cell.setEncoding(HSSFCell.ENCODING_UTF_16); cell.setCellType(HSSFCell.CELL_TYPE_STRING); cell.setCellValue(title); titleFlag++; } HSSFCell cell = rTitle.createCell((short)(titleFlag)); cell.setCellStyle(titleStyle); cell.setCellType(HSSFCell.CELL_TYPE_STRING); cell.setEncoding(HSSFCell.ENCODING_UTF_16); cell.setCellValue("總計"); titleFlag++; cell = rTitle.createCell((short)(titleFlag)); cell.setCellStyle(titleStyle); cell.setCellType(HSSFCell.CELL_TYPE_STRING); cell.setEncoding(HSSFCell.ENCODING_UTF_16); cell.setCellValue("鏈接"); cell = rTitle.createCell((short)(initCol-1)); cell.setCellStyle(titleStyle); cell.setCellType(HSSFCell.CELL_TYPE_STRING); cell.setEncoding(HSSFCell.ENCODING_UTF_16); cell.setCellValue("職員"); cell = rTitle.createCell((short)(initCol-2)); cell.setCellStyle(titleStyle); cell.setCellType(HSSFCell.CELL_TYPE_STRING); cell.setEncoding(HSSFCell.ENCODING_UTF_16); cell.setCellValue("部門"); int listFlag = initRow; for (Iterator it = lists.iterator(); it.hasNext();) { String name = (String)it.next(); List datas = (List)map.get(name); HSSFRow row = sheet.createRow(listFlag); cell = row.createCell((short)(initCol-1)); cell.setCellStyle(nameStyle); cell.setEncoding(HSSFCell.ENCODING_UTF_16); cell.setCellValue(name); cell = row.createCell((short)(initCol-2)); cell.setEncoding(HSSFCell.ENCODING_UTF_16); cell.setCellStyle(dataStyle); int dataFlag = initCol; System.out.println("datas=" + datas); for (Iterator ite = datas.iterator(); ite.hasNext();) { int data = ((Integer)ite.next()).intValue(); cell = row.createCell((short)dataFlag); cell.setCellStyle(dataStyle); cell.setCellType(HSSFCell.CELL_TYPE_NUMERIC); cell.setCellValue(data); dataFlag++; } cell = row.createCell((short)(dataFlag)); cell.setCellType(HSSFCell.CELL_TYPE_FORMULA); cell.setCellFormula("SUM(" + getColLetter(initCol) + (listFlag+1) + ":" + getColLetter(dataFlag-1) + (listFlag+1) + ")"); cell.setCellStyle(nameStyle); dataFlag++; cell = row.createCell((short)(dataFlag)); cell.setCellType(HSSFCell.CELL_TYPE_FORMULA); cell.setCellFormula("HYPERLINK(/"http://www.xxxxx.com/xxx.jsp?id=1/",/"homepage/")"); cell.setCellStyle(linkStyle); listFlag++; } sheet.getRow(initRow).getCell((short)(initCol-2)).setCellValue("武裝部"); sheet.addMergedRegion(new Region(initRow, (short)(initCol-2), initRow + lists.size() - 1, (short)(initCol-2))); return workbook; } /** * 將列的索引換算成ABCD字母,這個方法要在插入公式時用到。 * @param colIndex 列索引。 * @return ABCD字母。 */ private String getColLetter(int colIndex) { String ch = ""; if (colIndex < 26) ch = "" + (char)((colIndex) + 65); else ch = "" + (char)((colIndex) / 26 + 65 - 1) + (char)((colIndex) % 26 + 65); return ch; } /** * 獲得數據,組織爲HashMap. 這裏爲了演示方便,簡單生成了一些數據。在實際應用中,是從 * 數據庫中獲取數據的。 * @return 組織後的數據 */ private HashMap getDatas() { HashMap map = new HashMap(); List lists = new ArrayList(); List title = new ArrayList(); List a = new ArrayList(); List b = new ArrayList(); List c = new ArrayList(); for (int i = 1; i <= 8; i++) { title.add(i + "月"); a.add(new Integer((int)(Math.random() * 10))); b.add(new Integer((int)(Math.random() * 10))); c.add(new Integer((int)(Math.random() * 10))); } map.put("荊軻", a);lists.add("荊軻"); map.put("專諸", b); lists.add("專諸"); map.put("聶政", c); lists.add("聶政"); map.put("list", lists); map.put("title", title); System.out.println("map=" + map); return map; } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); }
} |