1. 程式人生 > >POI讀寫海量Excel

POI讀寫海量Excel

目前處理Excel的開源javaAPI主要有兩種,一是Jxl(Java Excel API),Jxl只支援Excel2003以下的版本。另外一種是Apache的Jakarta POI,相比於Jxl,POI對微軟辦公文件的支援更加強大,但是它使用複雜,上手慢。POI可支援更高的Excel版本2007。對Excel的讀取,POI有兩種模式,一是使用者模式,這種方式同Jxl的使用很類似,使用簡單,都是將檔案一次性讀到記憶體,檔案小的時候,沒有什麼問題,當檔案大的時候,就會出現OutOfMemory的記憶體溢位問題。第二種是事件驅動模式,拿Excel2007來說,其內容採用XML的格式來儲存,所以處理excel就是解析XML,而目前使用事件驅動模式解析XML的API是SAX(Simple API for XML),這種模型在讀取XML文件時,並沒有將整個文件讀入記憶體,而是按順序將整個文件解析完,在解析過程中,會主動產生事件交給程式中相應的處理函式來處理當前內容。因此這種方式對系統資源要求不高,可以處理海量資料。筆者曾經做過測試,這種方法處理一千萬條,每條五列的資料花費大約11分鐘。可見處理海量資料的檔案事件驅動是一個很好的方式。而本文中用到的AbstractExcel2003Reader、AbstractExcel2007Reader對Excel的讀取都是採用這種POI的事件驅動模式。至於Excel的寫操作,對較高版本的Excel2007,POI提供了很好的支援,主要流程是第一步構建工作薄和電子表格物件,第二步在一個流中構建文字檔案,第三步使用流中產生的資料替換模板中的電子表格。這種方式也可以處理海量資料檔案。AbstractExcel2007Writer就是使用這種方式進行寫操作。對於寫入較低版本的Excel2003,POI使用了使用者模式來處理,就是將整個文件載入進記憶體,如果資料量大的話就會出現記憶體溢位的問題,Excel2003Writer就是使用這種方式。據筆者的測試,如果資料量大於3萬條,每條8列的話,就會報OutOfMemory的錯誤。Excel2003中每個電子表格的記錄數必須在65536以下,否則就會發生異常。目前還沒有好的解決方案,建議對於海量資料寫入操作,儘量使用Excel2007。
  1. /** 
  2.  * 抽象Excel2003讀取器,通過實現HSSFListener監聽器,採用事件驅動模式解析excel2003 
  3.  * 中的內容,遇到特定事件才會觸發,大大減少了記憶體的使用。 
  4.  * 
  5.  */
  6. publicclass Excel2003Reader implements HSSFListener{  
  7.     privateint minColumns = -1;  
  8.     private POIFSFileSystem fs;  
  9.     privateint lastRowNumber;  
  10.     privateint lastColumnNumber;  
  11.     /** Should we output the formula, or the value it has? */
  12.     privateboolean outputFormulaValues = true;  
  13.     /** For parsing Formulas */
  14.     private SheetRecordCollectingListener workbookBuildingListener;  
  15.     //excel2003工作薄
  16.     private HSSFWorkbook stubWorkbook;  
  17.     // Records we pick up as we process
  18.     private SSTRecord sstRecord;  
  19.     private FormatTrackingHSSFListener formatListener;  
  20.     //表索引
  21.     privateint sheetIndex = -1;  
  22.     private BoundSheetRecord[] orderedBSRs;  
  23.     @SuppressWarnings("unchecked")  
  24.     private ArrayList boundSheetRecords = new ArrayList();  
  25.     // For handling formulas with string results
  26.     privateint nextRow;  
  27.     privateint nextColumn;  
  28.     privateboolean outputNextStringRecord;  
  29.     //當前行
  30.     privateint curRow = 0;  
  31.     //儲存行記錄的容器
  32.     private List<String> rowlist = new ArrayList<String>();;  
  33.     @SuppressWarnings"unused")  
  34.     private String sheetName;  
  35.     private IRowReader rowReader;  
  36.     publicvoid setRowReader(IRowReader rowReader){  
  37.         this.rowReader = rowReader;  
  38.     }  
  39.     /** 
  40.      * 遍歷excel下所有的sheet 
  41.      * @throws IOException 
  42.      */
  43.     publicvoid process(String fileName) throws IOException {  
  44.         this.fs = new POIFSFileSystem(new FileInputStream(fileName));  
  45.         MissingRecordAwareHSSFListener listener = new MissingRecordAwareHSSFListener(  
  46.                 this);  
  47.         formatListener = new FormatTrackingHSSFListener(listener);  
  48.         HSSFEventFactory factory = new HSSFEventFactory();  
  49.         HSSFRequest request = new HSSFRequest();  
  50.         if (outputFormulaValues) {  
  51.             request.addListenerForAllRecords(formatListener);  
  52.         } else {  
  53.             workbookBuildingListener = new SheetRecordCollectingListener(  
  54.                     formatListener);  
  55.             request.addListenerForAllRecords(workbookBuildingListener);  
  56.         }  
  57.         factory.processWorkbookEvents(request, fs);  
  58.     }  
  59.     /** 
  60.      * HSSFListener 監聽方法,處理 Record 
  61.      */
  62.     @SuppressWarnings("unchecked")  
  63.     publicvoid processRecord(Record record) {  
  64.         int thisRow = -1;  
  65.         int thisColumn = -1;  
  66.         String thisStr = null;  
  67.         String value = null;  
  68.         switch (record.getSid()) {  
  69.             case BoundSheetRecord.sid:  
  70.                 boundSheetRecords.add(record);  
  71.                 break;  
  72.             case BOFRecord.sid:  
  73.                 BOFRecord br = (BOFRecord) record;  
  74.                 if (br.getType() == BOFRecord.TYPE_WORKSHEET) {  
  75.                     // 如果有需要,則建立子工作薄
  76.                     if (workbookBuildingListener != null && stubWorkbook == null) {  
  77.                         stubWorkbook = workbookBuildingListener  
  78.                                 .getStubHSSFWorkbook();  
  79.                     }  
  80.                     sheetIndex++;  
  81.                     if (orderedBSRs == null) {  
  82.                         orderedBSRs = BoundSheetRecord  
  83.                                 .orderByBofPosition(boundSheetRecords);  
  84.                     }  
  85.                     sheetName = orderedBSRs[sheetIndex].getSheetname();  
  86.                 }  
  87.                 break;  
  88.             case SSTRecord.sid:  
  89.                 sstRecord = (SSTRecord) record;  
  90.                 break;  
  91.             case BlankRecord.sid:  
  92.                 BlankRecord brec = (BlankRecord) record;  
  93.                 thisRow = brec.getRow();  
  94.                 thisColumn = brec.getColumn();  
  95.                 thisStr = "";  
  96.                 rowlist.add(thisColumn, thisStr);  
  97.                 break;  
  98.             case BoolErrRecord.sid: //單元格為布林型別
  99.                 BoolErrRecord berec = (BoolErrRecord) record;  
  100.                 thisRow = berec.getRow();  
  101.                 thisColumn = berec.getColumn();  
  102.                 thisStr = berec.getBooleanValue()+"";  
  103.                 rowlist.add(thisColumn, thisStr);  
  104.                 break;  
  105.             case FormulaRecord.sid: //單元格為公式型別
  106.                 FormulaRecord frec = (FormulaRecord) record;  
  107.                 thisRow = frec.getRow();  
  108.                 thisColumn = frec.getColumn();  
  109.                 if (outputFormulaValues) {  
  110.                     if (Double.isNaN(frec.getValue())) {  
  111.                         // Formula result is a string
  112.                         // This is stored in the next record
  113.                         outputNextStringRecord = true;  
  114.                         nextRow = frec.getRow();  
  115.                         nextColumn = frec.getColumn();  
  116.                     } else {  
  117.                         thisStr = formatListener.formatNumberDateCell(frec);  
  118.                     }  
  119.                 } else {  
  120.                     thisStr = '"' + HSSFFormulaParser.toFormulaString(stubWorkbook,  
  121.                             frec.getParsedExpression()) + '"';  
  122.                 }  
  123.                 rowlist.add(thisColumn,thisStr);  
  124.                 break;  
  125.             case StringRecord.sid://單元格中公式的字串
  126.                 if (outputNextStringRecord) {  
  127.                     // String for formula
  128.                     StringRecord srec = (StringRecord) record;  
  129.                     thisStr = srec.getString();  
  130.                     thisRow = nextRow;  
  131.                     thisColumn = nextColumn;  
  132.                     outputNextStringRecord = false;  
  133.                 }  
  134.                 break;  
  135.             case LabelRecord.sid:  
  136.                 LabelRecord lrec = (LabelRecord) record;  
  137.                 curRow = thisRow = lrec.getRow();  
  138.                 thisColumn = lrec.getColumn();  
  139.                 value = lrec.getValue().trim();  
  140.                 value = value.equals("")?" ":value;  
  141.                 this.rowlist.add(thisColumn, value);  
  142.                 break;  
  143.             case LabelSSTRecord.sid:  //單元格為字串型別
  144.                 LabelSSTRecord lsrec = (LabelSSTRecord) record;  
  145.                 curRow = thisRow = lsrec.getRow();  
  146.                 thisColumn = lsrec.getColumn();  
  147.                 if (sstRecord == null) {  
  148.                     rowlist.add(thisColumn, " ");  
  149.                 } else {  
  150.                     value =  sstRecord  
  151.                     .getString(lsrec.getSSTIndex()).toString().trim();  
  152.                     value = value.equals("")?" ":value;  
  153.                     rowlist.add(thisColumn,value);  
  154.                 }  
  155.                 break;  
  156.             case NumberRecord.sid:  //單元格為數字型別
  157.                 NumberRecord numrec = (NumberRecord) record;  
  158.                 curRow = thisRow = numrec.getRow();  
  159.                 thisColumn = numrec.getColumn();  
  160.                 value = formatListener.formatNumberDateCell(numrec).trim();  
  161.                 value = value.equals("")?" ":value;  
  162.                 // 向容器加入列值
  163.                 rowlist.add(thisColumn, value);  
  164.                 break;  
  165.             default:  
  166.                 break;  
  167.         }  
  168.         // 遇到新行的操作
  169.         if (thisRow != -1 && thisRow != lastRowNumber) {  
  170.             lastColumnNumber = -1;  
  171.         }  
  172.         // 空值的操作
  173.         if (record instanceof MissingCellDummyRecord) {  
  174.             MissingCellDummyRecord mc = (MissingCellDummyRecord) record;  
  175.             curRow = thisRow = mc.getRow();  
  176.             thisColumn = mc.getColumn();  
  177.             rowlist.add(thisColumn," ");  
  178.         }  
  179.         // 更新行和列的值
  180.         if (thisRow > -1)  
  181.             lastRowNumber = thisRow;  
  182.         if (thisColumn > -1)  
  183.             lastColumnNumber = thisColumn;  
  184.         // 行結束時的操作
  185.         if (record instanceof LastCellOfRowDummyRecord) {  
  186.             if (minColumns > 0) {  
  187.                 // 列值重新置空
  188.                 if (lastColumnNumber == -1) {  
  189.                     lastColumnNumber = 0;  
  190.                 }  
  191.             }  
  192.             lastColumnNumber = -1;  
  193.                 // 每行結束時, 呼叫getRows() 方法
  194.             rowReader.getRows(sheetIndex,curRow, rowlist);  
  195.             // 清空容器
  196.             rowlist.clear();  
  197.         }  
  198.     }  
  199. }  
  1. /** 
  2.  * 抽象Excel2007讀取器,excel2007的底層資料結構是xml檔案,採用SAX的事件驅動的方法解析 
  3.  * xml,需要繼承DefaultHandler,在遇到檔案內容時,事件會觸發,這種做法可以大大降低 
  4.  * 記憶體的耗費,特別使用於大資料量的檔案。 
  5.  * 
  6.  */
  7. publicclass Excel2007Reader extends DefaultHandler {  
  8.     //共享字串表
  9.     private SharedStringsTable sst;  
  10.     //上一次的內容
  11.     private String lastContents;  
  12.     privateboolean nextIsString;  
  13.     privateint sheetIndex = -1;  
  14.     private List<String> rowlist = new ArrayList<String>();  
  15.     //當前行
  16.     privateint curRow = 0;  
  17.     //當前列
  18.     privateint curCol = 0;  
  19.     //日期標誌
  20.     privateboolean dateFlag;  
  21.     //數字標誌
  22.     privateboolean numberFlag;  
  23.     privateboolean isTElement;  
  24.     private IRowReader rowReader;  
  25.     publicvoid setRowReader(IRowReader rowReader){  
  26.         this.rowReader = rowReader;  
  27.     }  
  28.     /**只遍歷一個電子表格,其中sheetId為要遍歷的sheet索引,從1開始,1-3 
  29.      * @param filename 
  30.      * @param sheetId 
  31.      * @throws Exception 
  32.      */
  33.     publicvoid processOneSheet(String filename,int sheetId) throws Exception {  
  34.         OPCPackage pkg = OPCPackage.open(filename);  
  35.         XSSFReader r = new XSSFReader(pkg);  
  36.         SharedStringsTable sst = r.getSharedStringsTable();  
  37.         XMLReader parser = fetchSheetParser(sst);  
  38.         // 根據 rId# 或 rSheet# 查詢sheet
  39.         InputStream sheet2 = r.getSheet("rId"+sheetId);  
  40.         sheetIndex++;  
  41.         InputSource sheetSource = new InputSource(sheet2);  
  42.         parser.parse(sheetSource);  
  43.         sheet2.close();  
  44.     }  
  45.     /** 
  46.      * 遍歷工作簿中所有的電子表格 
  47.      * @param filename 
  48.      * @throws Exception 
  49.      */
  50.     publicvoid process(String filename) throws Exception {  
  51.         OPCPackage pkg = OPCPackage.open(filename);  
  52.         XSSFReader r = new XSSFReader(pkg);  
  53.         SharedStringsTable sst = r.getSharedStringsTable();  
  54.         XMLReader parser = fetchSheetParser(sst);  
  55.         Iterator<InputStream> sheets = r.getSheetsData();  
  56.         while (sheets.hasNext()) {  
  57.             curRow = 0;  
  58.             sheetIndex++;  
  59.             InputStream sheet = sheets.next();  
  60.             InputSource sheetSource = new InputSource(sheet);  
  61.             parser.parse(sheetSource);  
  62.             sheet.close();  
  63.         }  
  64.     }  
  65.     public XMLReader fetchSheetParser(SharedStringsTable sst)  
  66.             throws SAXException {  
  67.         XMLReader parser = XMLReaderFactory  
  68.                 .createXMLReader("org.apache.xerces.parsers.SAXParser");  
  69.         this.sst = sst;  
  70.         parser.setContentHandler(this);  
  71.         return parser;  
  72.     }  
  73.     publicvoid startElement(String uri, String localName, String name,  
  74.             Attributes attributes) throws SAXException {  
  75.         // c => 單元格
  76.         if ("c".equals(name)) {  
  77.             // 如果下一個元素是 SST 的索引,則將nextIsString標記為true
  78.             String cellType = attributes.getValue("t");  
  79.             if ("s".equals(cellType)) {  
  80.                 nextIsString = true;  
  81.             } else {  
  82.                 nextIsString = false;  
  83.             }  
  84.             //日期格式
  85.             String cellDateType = attributes.getValue("s");  
  86.             if ("1".equals(cellDateType)){  
  87.                 dateFlag = true;  
  88.             } else {  
  89.                 dateFlag = false;  
  90.             }  
  91.             String cellNumberType = attributes.getValue("s");  
  92.             if("2".equals(cellNumberType)){  
  93.                 numberFlag = true;  
  94.             } else {  
  95.                 numberFlag = false;  
  96.             }  
  97.         }  
  98.         //當元素為t時
  99.         if("t".equals(name)){  
  100.             isTElement = true;  
  101.         } else {  
  102.             isTElement = false;  
  103.         }  
  104.         // 置空
  105.         lastContents = "";  
  106.     }  
  107.     publicvoid endElement(String uri, String localName, String name)  
  108.             throws SAXException {  
  109.         // 根據SST的索引值的到單元格的真正要儲存的字串
  110.         // 這時characters()方法可能會被呼叫多次
  111.         if (nextIsString) {  
  112.             try {  
  113.                 int idx = Integer.parseInt(lastContents);  
  114.                 lastContents = new XSSFRichTextString(sst.getEntryAt(idx))  
  115.                         .toString();  
  116.             } catch (Exception e) {  
  117.             }  
  118.         }   
  119.         //t元素也包含字串
  120.         if(isTElement){  
  121.             String value = lastContents.trim();  
  122.             rowlist.add(curCol, value);  
  123.             curCol++;  
  124.             isTElement = false;  
  125.             // v => 單元格的值,如果單元格是字串則v標籤的值為該字串在SST中的索引
  126.             // 將單元格內容加入rowlist中,在這之前先去掉字串前後的空白符
  127.         } elseif ("v".equals(name)) {  
  128.             String value = lastContents.trim();  
  129.             value = value.equals("")?" ":value;  
  130.             //日期格式處理
  131.             if(dateFlag){  
  132.                  Date date = HSSFDateUtil.getJavaDate(Double.valueOf(value));  
  133.                  SimpleDateFormat dateFormat = new SimpleDateFormat(  
  134.                  "dd/MM/yyyy");  
  135.                  value = dateFormat.format(date);  
  136.             }   
  137.             //數字型別處理
  138.             if(numberFlag){  
  139.                 BigDecimal bd = new BigDecimal(value);  
  140.                 value = bd.setScale(3,BigDecimal.ROUND_UP).toString();  
  141.             }  
  142.             rowlist.add(curCol, value);  
  143.             curCol++;  
  144.         }else {  
  145.             //如果標籤名稱為 row ,這說明已到行尾,呼叫 optRows() 方法
  146.             if (name.equals("row")) {  
  147.                 rowReader.getRows(sheetIndex,curRow,rowlist);  
  148.                 rowlist.clear();  
  149.                 curRow++;  
  150.                 curCol = 0;  
  151.             }  
  152.         }  
  153.     }  
  154.     publicvoid characters(char[] ch, int start, int length)  
  155.             throws SAXException {  
  156.         //得到單元格內容的值
  157.         lastContents += new String(ch, start, length);  
  158.     }  
  159. }  


  1. publicclass ExcelReaderUtil {  
  2.     //excel2003副檔名
  3.     publicstaticfinal String EXCEL03_EXTENSION = ".xls";  
  4.     //excel2007副檔名
  5.     publicstaticfinal String EXCEL07_EXTENSION = ".xlsx";  
  6.     /** 
  7.      * 讀取Excel檔案,可能是03也可能是07版本 
  8.      * @param excel03 
  9.      * @param excel07 
  10.      * @param fileName 
  11.      * @throws Exception  
  12.      */
  13.     publicstaticvoid readExcel(IRowReader reader,String fileName) throws Exception{  
  14.         // 處理excel2003檔案
  15.         if (fileName.endsWith(EXCEL03_EXTENSION)){  
  16.             Excel2003Reader excel03 = new Excel2003Reader();  
  17.             excel03.setRowReader(reader);  
  18.             excel03.process(fileName);  
  19.         // 處理excel2007檔案
  20.         } elseif (fileName.endsWith(EXCEL07_EXTENSION)){  
  21.             Excel2007Reader excel07 = new Excel2007Reader();  
  22.             excel07.setRowReader(reader);  
  23.             excel07.process(fileName);  
  24.         } else {  
  25.             thrownew  Exception("檔案格式錯誤,fileName的副檔名只能是xls或xlsx。");  
  26.         }  
  27.     }  
  28. }  


  1. publicinterface IRowReader {  
  2.     /**業務邏輯實現方法 
  3.      * @param sheetIndex 
  4.      * @param curRow 
  5.      * @param rowlist 
  6.      */
  7.     publicvoid getRows(int sheetIndex,int curRow, List<String> rowlist);  
  8. }  


  1. publicclass RowReader implements IRowReader{  
  2.     /* 業務邏輯實現方法 
  3.      * @see com.eprosun.util.excel.IRowReader#getRows(int, int, java.util.List) 
  4.      */
  5.     publicvoid getRows(int sheetIndex, int curRow, List<String> rowlist) {  
  6.         // TODO Auto-generated method stub
  7.         System.out.print(curRow+" ");  
  8.         for (int i = 0; i < rowlist.size(); i++) {  
  9.             System.out.print(rowlist.get(i) + " ");  
  10.         }  
  11.         System.out.println();  
  12.     }  
  13. }  

  1. publicclass Main {  
  2.     publicstaticvoid main(String[] args) throws Exception {  
  3.         IRowReader reader = new RowReader();  
  4.         //ExcelReaderUtil.readExcel(reader, "F://te03.xls");
  5.         ExcelReaderUtil.readExcel(reader, "F://test07.xlsx");  
  6.     }  
  7. }  


  1. publicclass Excel2003Writer {  
  2.     /** 
  3.      * @param args 
  4.      */
  5.     publicstaticvoid main(String[] args) {  
  6.         try{      
  7.             System.out.println("開始寫入excel2003....");  
  8.             writeExcel("tes2003.xls");  
  9.             System.out.println("寫完xcel2003");  
  10.         } catch (IOException e) {  
  11.         }  
  12.     }  
  13.     /** 
  14.      * 寫入excel並填充內容,一個sheet只能寫65536行以下,超出會報異常,寫入時建議使用AbstractExcel2007Writer 
  15.      * @param fileName 
  16.      * @throws IOException 
  17.      */
  18.     publicstaticvoid writeExcel(String fileName) throws IOException{  
  19.             // 建立excel2003物件
  20.             Workbook wb = new HSSFWorkbook();  
  21.             // 設定檔案放置路徑和檔名
  22.             FileOutputStream fileOut = new FileOutputStream(fileName);  
  23.             // 建立新的表單
  24.             Sheet sheet = wb.createSheet("newsheet");  
  25.             // 建立新行
  26.             for(int i=0;i<20000;i++){  
  27.                 Row row = sheet.createRow(i);  
  28.                 // 建立單元格
  29.                 Cell cell = row.createCell(0);  
  30.                 // 設定單元格值
  31.                 cell.setCellValue(1);  
  32.                 row.createCell(1).setCellValue(1+i);  
  33.                 row.createCell(2).setCellValue(true);  
  34.                 row.createCell(3).setCellValue(0.43d);  
  35.                 row.createCell(4).setCellValue('d');  
  36.                 row.createCell(5).setCellValue("");  
  37.                 row.createCell(6).setCellValue("第七列"+i);  
  38.                 row.createCell(7).setCellValue("第八列"+i);  
  39.             }  
  40.             wb.write(fileOut);  
  41.             fileOut.close();  
  42.     }  
  43. }  


  1. /** 
  2.  * 抽象excel2007讀入器,先構建.xlsx一張模板,改寫模板中的sheet.xml,使用這種方法 
  3.  * 寫入.xlsx檔案,不需要太大的記憶體 
  4.  * 
  5.  */
  6. publicabstractclass AbstractExcel2007Writer {  
  7.     private SpreadsheetWriter sw;  
  8.     /** 
  9.      * 寫入電子表格的主要流程 
  10.      * @param fileName 
  11.      * @throws Exception 
  12.      */
  13.     publicvoid process(String fileName) throws Exception{  
  14.         // 建立工作簿和電子表格物件
  15.         XSSFWorkbook wb = new XSSFWorkbook();  
  16.         XSSFSheet sheet = wb.createSheet("sheet1");  
  17.         // 持有電子表格資料的xml檔名 例如 /xl/worksheets/sheet1.xml
  18.         String sheetRef = sheet.getPackagePart().getPartName().getName();  
  19.         // 儲存模板
  20.         FileOutputStream os = new FileOutputStream("template.xlsx");  
  21.         wb.write(os);  
  22.         os.close();  
  23.         // 生成xml檔案
  24.         File tmp = File.createTempFile("sheet"".xml");  
  25.         Writer fw = new FileWriter(tmp);  
  26.         sw = new SpreadsheetWriter(fw);  
  27.         generate();  
  28.         fw.close();  
  29.         // 使用產生的資料替換模板
  30.         File templateFile = new File("template.xlsx");  
  31.         FileOutputStream out = new FileOutputStream(fileName);  
  32.         substitute(templateFile, tmp, sheetRef.substring(1), out);  
  33.         out.close();  
  34.         //刪除檔案之前呼叫一下垃圾回收器,否則無法刪除模板檔案
  35.         System.gc();  
  36.         // 刪除臨時模板檔案
  37.         if (templateFile.isFile()&&templateFile.exists()){  
  38.             templateFile.delete();  
  39.         }  
  40.     }  
  41.     /** 
  42.      * 類使用者應該使用此方法進行寫操作 
  43.      * @throws Exception 
  44.      */
  45.     publicabstractvoid generate() throws Exception;  
  46.     publicvoid beginSheet() throws IOException {  
  47.         sw.beginSheet();  
  48.     }  
  49.     publicvoid insertRow(int rowNum) throws IOException {  
  50.         sw.insertRow(rowNum);  
  51.     }  
  52.     publicvoid createCell(int columnIndex, String value) throws IOException {  
  53.         sw.createCell(columnIndex, value, -1);  
  54.     }  
  55.     publicvoid createCell(int columnIndex, double value) throws IOException {  
  56.         sw.createCell(columnIndex, value, -1);  
  57.     }  
  58.     publicvoid endRow() throws IOException {  
  59.         sw.endRow();  
  60.     }  
  61.     publicvoid endSheet() throws IOException {  
  62.         sw.endSheet();  
  63.     }  
  64.     /** 
  65.      * 
  66.      * @param zipfile the template file 
  67.      * @param tmpfile the XML file with the sheet data 
  68.      * @param entry the name of the sheet entry to substitute, e.g. xl/worksheets/sheet1.xml 
  69.      * @param out the stream to write the result to 
  70.      */
  71.     privatestaticvoid substitute(File zipfile, File tmpfile, String entry,  
  72.             OutputStream out) throws IOException {  
  73.         ZipFile zip = new ZipFile(zipfile);  
  74.         ZipOutputStream zos = new ZipOutputStream(out);  
  75.         @SuppressWarnings("unchecked")  
  76.         Enumeration<ZipEntry> en = (Enumeration<ZipEntry>) zip.entries();  
  77.         while