`
longgangbai
  • 浏览: 7338859 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

POI3.8组件研究(四)--Event API (HSSF Only)事件的解析

阅读更多

    通过eventusermodel读取文件
  
  通过eventusermodel读取文件要比使用usermodel复杂得多,但效率也要高不少,因为它要求应用程序一边读取数据,一边处理数据。
  
  eventusermodel实际上模拟了DOM环境下SAX处理XML文档的办法,应用程序首先要注册期望处理的数据,eventusermodel将在遇到匹配的数据结构时回调应用程序注册的方法。使用eventusermodel最大的困难在于你必须熟悉Excel工作簿的内部结构。
  
  在HSSF中,低层次的二进制结构称为记录(Record)。记录有不同的类型,每一种类型由org.apache.poi.hssf.record包中的一个Java类描述。例如,BOFRecord记录表示Workbook或Sheet区域的开始,RowRecord表示有一个行存在并保存其样式信息。
  
  所有具有CellValueRecordInterface接口的记录表示Excel的单元格,包括NumericRecord、LabelSSTRecord和FormulaRecord(还有其他一些,其中部分已被弃置不用,部分用于优化处理,但一般而言,HSSF可以转换它们)。

解析Excel2003 的event user-model必须继承 HSSFListener;

public class UserModelEventListener implements HSSFListener ;

 

 

package com.easyway.excel.events;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.collections.CollectionUtils;
import org.apache.poi.hssf.eventusermodel.EventWorkbookBuilder.SheetRecordCollectingListener;
import org.apache.poi.hssf.eventusermodel.HSSFListener;
import org.apache.poi.hssf.record.BOFRecord;
import org.apache.poi.hssf.record.BlankRecord;
import org.apache.poi.hssf.record.BoolErrRecord;
import org.apache.poi.hssf.record.BoundSheetRecord;
import org.apache.poi.hssf.record.FormulaRecord;
import org.apache.poi.hssf.record.LabelSSTRecord;
import org.apache.poi.hssf.record.NumberRecord;
import org.apache.poi.hssf.record.RowRecord;
import org.apache.poi.hssf.record.SSTRecord;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
 * 基于POI HSSF的eventmodel 模型的时间解析方式
 *   优点:解析数据相当快。
 *   缺点:1.仅仅支持97~2003版本的excel,不支持2007版本的excel。
 *         2.只能读Excel中一个Sheet页面。
 * 
 * @Title: 
 * @Description: 实现TODO
 * @Copyright:Copyright (c) 2011
 * @Company:易程科技股份有限公司
 * @Date:2012-6-14
 * @author  longgangbai
 * @version 1.0
 */
public class UserModelEventListener implements HSSFListener {
	private static Logger logger=LoggerFactory.getLogger(UserModelEventListener.class);
	private SSTRecord sstrec;
	/** Should we output the formula, or the value it has? */
	private boolean outputFormulaValues = true;
	/** For parsing Formulas */
	private SheetRecordCollectingListener workbookBuildingListener;

    //当前Sheet的内容
    private List<Map<String,Object>> currentSheetDataMap=new ArrayList<Map<String,Object>>();
    //列对应的字段
	private static String[] trianListheadTitle=new String[]{"trainCode","firstStation","lastStation","startStation","arriveStation","startTime","arriveTime","fistLevelPrice","secondLevelPrice","km","useDate"};

    //一行记录
    private Map<String,Object> currentSheetRowDataMap=new HashMap<String,Object>();
    private int curRowNum=0;
    private int ignoreRowNum=1;
    private int sheetNo=0;
    
	@Override
	public void processRecord(org.apache.poi.hssf.record.Record record) {
		switch (record.getSid()) {
		
		case BOFRecord.sid:
			BOFRecord bof = (BOFRecord) record;
			//顺序进入新的Workbook  
			if (bof.getType() == bof.TYPE_WORKBOOK) {
				logger.debug("开始解析excel 文档.....");
			//顺序进入新的Worksheet,因为Event API不会把Excel文件里的所有数据结构都关联起来,
			//所以这儿一定要记录现在进入第几个sheet了。
			} else if (bof.getType() == bof.TYPE_WORKSHEET) {
				//读取新的一个Sheet页
				logger.debug("开始解析sheet页面内容...");
				System.out.println("sheetNo="+sheetNo);
				sheetNo++;
				currentSheetDataMap=new ArrayList<Map<String,Object>>();
			}
			break;
	    //开始解析Sheet的信息,记录sheet,这儿会把所有的sheet都顺序打印出来,如果有多个sheet的话,可以顺序记入到一个List里   
		case BoundSheetRecord.sid:
			BoundSheetRecord bsr = (BoundSheetRecord) record;
			System.out.println("sheetName="+bsr.getSheetname());
			logger.debug("New sheet named: " + bsr.getSheetname());
			break;
		//执行行记录事件
		case RowRecord.sid:
			RowRecord rowrec = (RowRecord) record;
			logger.debug("记录开始, first column at "
					+ rowrec.getFirstCol() + " last column at "
					+ rowrec.getLastCol());
			break;
		// SSTRecords store a array of unique strings used in Excel.
		case SSTRecord.sid:
			sstrec = (SSTRecord) record;
			for (int k = 0; k < sstrec.getNumUniqueStrings(); k++) {
				logger.debug("String table value " + k + " = "
						+ sstrec.getString(k));
			}
			break;
			
		//发现数字类型的cell,因为数字和日期都是用这个格式,所以下面一定要判断是不是日期格式,另外默认的数字也会被视为日期格式,所以如果是数字的话,一定要明确指定格式!!!!!!!   
		case NumberRecord.sid:
				NumberRecord nr = (NumberRecord) record;
				//HSSFDateUtil.isInternalDateFormat(nr.getXFIndex())  判断是否为时间列
				int column=nr.getColumn();
				if(column==5||column==6){
					addDataAndrChangeRow(nr.getRow(),nr.getColumn(),getTime(nr.getValue()));
				}else{
					addDataAndrChangeRow(nr.getRow(),nr.getColumn(),(int)nr.getValue());
				}
				break;
		//发现字符串类型,这儿要取字符串的值的话,跟据其index去字符串表里读取   
		case LabelSSTRecord.sid:
			LabelSSTRecord lsr = (LabelSSTRecord)record; 
			addDataAndrChangeRow(lsr.getRow(),lsr.getColumn(), sstrec.getString(lsr.getSSTIndex()));
			logger.debug("文字列:"+sstrec.getString(lsr.getSSTIndex())+", 行:"+lsr.getRow()+", 列:"+lsr.getColumn());   
			break;
	    case BoolErrRecord.sid: //解析boolean错误信息
            BoolErrRecord ber = (BoolErrRecord)record;   
            if(ber.isBoolean()){   
            	addDataAndrChangeRow(ber.getRow(),ber.getColumn(), ber.getBooleanValue());
            	logger.debug("Boolean:"+ber.getBooleanValue()+", 行:"+ber.getRow()+", 列:"+ber.getColumn());   
            }   
            if(ber.isError()){   
            	logger.debug("Error:"+ber.getErrorValue()+", 行:"+ber.getRow()+", 列:"+ber.getColumn());   
            }   
            break;   
         //空白记录的信息
        case BlankRecord.sid: 
            BlankRecord br = (BlankRecord)record;   
            addDataAndrChangeRow(br.getRow(),br.getColumn(), "");
            logger.debug("空。 行:"+br.getRow()+", 列:"+br.getColumn());   
            break;   
        case FormulaRecord.sid: //数式   
            FormulaRecord fr = (FormulaRecord)record;  
            addDataAndrChangeRow(fr.getRow(),fr.getColumn(), fr.getValue());
            logger.debug("数字 。 行:"+fr.getRow()+", 列:"+fr.getColumn());  
            break;  
		}
	}
	/** 
     * HH:MM格式时间的数字转换方法</li> 
     * @param sNum 
     * @return 
     */ 
    private static String getTime(double daynum) 
    { 
        double totalSeconds=daynum*86400.0D;
        //总的分钟数
        int seconds =(int)totalSeconds/60;
        //实际小时数
        int hours =seconds/60;
        int minutes = seconds-hours*60;
        //剩余的实际分钟数
        StringBuffer sb=new StringBuffer();
        if(String.valueOf(hours).length()==1){
        	sb.append("0"+hours);
        }else{
        	sb.append(hours);
        }
        sb.append(":");
        if(String.valueOf(minutes).length()==1){
        	sb.append("0"+minutes);
        }else{
        	sb.append(minutes);
        }
        return sb.toString();
    } 
	/**
	 *  添加数据记录并检查是否换行
	 * @param row 实际当前行号
	 * @param col 实际记录当前列
	 * @param value  当前cell的值
	 */
	public void addDataAndrChangeRow(int row,int col,Object value){
		//当前行如果大于实际行表示改行忽略,不记录
		if(curRowNum!=row){
			if(CollectionUtils.isEmpty(currentSheetDataMap)){
				 currentSheetDataMap=new ArrayList<Map<String,Object>>();
			}
			currentSheetDataMap.add(currentSheetRowDataMap);
			logger.debug("行号:"+curRowNum +" 行内容:"+currentSheetRowDataMap.toString());
			logger.debug("\n");
			currentSheetRowDataMap=new HashMap<String,Object>();
			currentSheetRowDataMap.put(trianListheadTitle[col], value);
			logger.debug(row+":"+col+"  "+value+"\r");
			curRowNum=row;
		}else{
			currentSheetRowDataMap.put(trianListheadTitle[col], value);
			logger.debug(row+":"+col+"  "+value+"\r");
		}
	}
	public List<Map<String, Object>> getCurrentSheetDataMap() {
		return currentSheetDataMap;
	}
	public void setCurrentSheetDataMap(List<Map<String, Object>> currentSheetDataMap) {
		this.currentSheetDataMap = currentSheetDataMap;
	}
	public Map<String, Object> getCurrentSheetRowDataMap() {
		return currentSheetRowDataMap;
	}
	public void setCurrentSheetRowDataMap(Map<String, Object> currentSheetRowDataMap) {
		this.currentSheetRowDataMap = currentSheetRowDataMap;
	}
	public int getCurRowNum() {
		return curRowNum;
	}
	public void setCurRowNum(int curRowNum) {
		this.curRowNum = curRowNum;
	}
	public int getIgnoreRowNum() {
		return ignoreRowNum;
	}
	public void setIgnoreRowNum(int ignoreRowNum) {
		this.ignoreRowNum = ignoreRowNum;
	}

}

 

 

测试代码如下:

package com.easyway.excel.events;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;

import org.apache.poi.hssf.eventusermodel.FormatTrackingHSSFListener;
import org.apache.poi.hssf.eventusermodel.HSSFEventFactory;
import org.apache.poi.hssf.eventusermodel.HSSFRequest;
import org.apache.poi.hssf.eventusermodel.MissingRecordAwareHSSFListener;
import org.apache.poi.hssf.eventusermodel.EventWorkbookBuilder.SheetRecordCollectingListener;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;

public class UserModelEventMain {
	
	/**
	 * 解析Excel2003的事件解析格式
	 * @param args
	 * @throws IOException
	 */
	public static void main(String[] args) throws IOException {
		UserModelEventListener xlsEventListener=new UserModelEventListener();
		
		MissingRecordAwareHSSFListener listener = new MissingRecordAwareHSSFListener(
				xlsEventListener);
		FormatTrackingHSSFListener formatListener = new FormatTrackingHSSFListener(listener);
		
		
		//创建一个excel输入流
		FileInputStream fin = new FileInputStream("C:\\station2station.xls");
		//创建一个 org.apache.poi.poifs.filesystem.Filesystem
		POIFSFileSystem poifs = new POIFSFileSystem(fin);
		//将excel 2003格式POI文档输入流
		InputStream din = poifs.createDocumentInputStream("Workbook");
		//这儿为所有类型的Record都注册了监听器,如果需求明确的话,可以用addListener方法,并指定所需的Record类型   
		HSSFRequest req = new HSSFRequest();
		//添加监听记录的事件 
		req.addListenerForAllRecords(xlsEventListener);
		
		boolean outputFormulaValues=true;
		if (outputFormulaValues) {
			req.addListenerForAllRecords(formatListener);
		} else {
			SheetRecordCollectingListener	workbookBuildingListener = new SheetRecordCollectingListener(
					formatListener);
			req.addListenerForAllRecords(workbookBuildingListener);
		}
		
		//创建时间工厂
		HSSFEventFactory factory = new HSSFEventFactory();
		//处理基于时间文档流
		factory.processEvents(req, din);
		//关闭文件流
		fin.close();
		//关闭基于POI文档流
		din.close();
		
		List<Map<String, Object>> sheetDataList=xlsEventListener.getCurrentSheetDataMap();
		
		System.out.println("Excel Sheet记录数"+sheetDataList.size()+" 内容"+sheetDataList.toString());
	}

}

 

测试excel文件如下:

 

 

 

分享到:
评论
1 楼 yaotao_66 2015-11-20  
为什么最后一行数据保存不了,最终的结果少了一行数据。

相关推荐

    POI3.8组件研究(七)--基于XSSF and SAX (Event API)事件的解析

    标题 "POI3.8组件研究(七)--基于XSSF and SAX (Event API)事件的解析" 提到了Apache POI库的一个高级话题,主要关注的是如何使用XSSF(XML Spreadsheet Formatting Streams)和SAX(Simple API for XML)的Event ...

    poi-examples-3.8-beta5-sources.jar.zip

    在Excel处理方面,POI提供了一个叫做HSSF(Horrible Spreadsheet Format)的API来处理旧版的.BIFF格式(Excel 97-2007),而XSSF API则用来处理OOXML (.xlsx) 格式的Excel文件。 在 poi-examples-3.8-beta5-sources...

    POI3.8和3.8的API

    6. **高效率读取**:除了用户模型API,POI还提供了低级别处理,如Record和Event API,可以高效地读取文件的原始记录,适合进行深度分析或定制化操作。 7. **模板处理**:POI可以用于创建基于模板的文件生成,例如,...

    poi-3.8-3.9-3.10.rar

    5. **性能优化**:在处理大量数据时,可以使用POI的事件模型(Event API,如SXSSF)或者HSSF的RecordFactory,这些方法不需要加载整个工作簿到内存,而是按需读取,显著降低了内存占用。 6. **错误处理与兼容性**:...

    POI_3.8_API

    - 使用POI的事件模型(EventModel API)进行低内存处理,仅在需要时读取和写入数据。 7. **文档兼容性**: - POI支持读取和写入不同版本的Office文件,但新版本的API通常提供更好的功能和性能。 总之,Apache ...

    POI3.8以及3.9的API

    标题中的"POI3.8以及3.9的API"指的是这两个版本的API接口文档,是开发者理解和使用POI库的关键资源。API文档详尽地列出了各种类、方法和接口,使得开发者能够有效地利用POI进行文件操作。 在POI 3.8和3.9中,主要...

    HSSF POI 3.8帮助文档 英文版

    9. **事件模型**:POI还提供了事件模型(Event API),可以在读取文件时只处理感兴趣的单元格,从而降低内存需求。 10. **错误处理**:在编程过程中,可能会遇到各种异常,如文件格式不正确、内存不足等。理解和...

    poi-3.8-20120326.jar

    4. **事件模型**:对于大文件处理,Apache POI提供了事件模型(Event API),如SXSSF(Streaming Usermodel API)和XSSFEventBasedExcelReader,以减少内存消耗。这些API适用于处理大量数据或服务器端的批量处理场景...

    poi3.8资源包

    4. **事件模型(Event API)**: POI还提供了一个事件模型,称为SXSSF事件模型,它在处理非常大的Excel文件时更为高效。通过监听器模式,开发者可以只处理必要的数据,而不是将整个文件加载到内存中。 5. **样式和...

    poi-3.10-FINAL-20140208 jar包

    4. **事件模型(Event API)**: - 对于大文件,直接加载到内存可能造成资源消耗过大。为此,POI提供了事件模型,通过`SXSSF`(Streaming Usermodel API)只在内存中保留最近使用的数据,从而减少内存使用。 5. **...

    poi-3.8.zip

    - 对于复杂操作,考虑使用Apache POI的事件模型(Event API),它只处理需要的数据,显著降低内存需求。 Apache POI 3.8版本虽然已经较旧,但在许多项目中仍然被广泛使用,因为它稳定且功能齐全。然而,为了获得...

    poi3.9读写excel兼容03和07版本

    5. **事件模型**:对于大文件,可以使用事件模型(Event API)以减少内存消耗。它不加载整个文件到内存,而是只处理必要的数据。 6. **数据公式处理**:POI支持读写Excel公式,包括计算公式的结果。 7. **流式读写...

    poi解析excel jar包

    10. **事件模型(Event API)**: 如果处理大型Excel文件,为了提高性能,可以使用事件模型,只读取需要的数据,而不是一次性加载整个文件。 11. **内存管理**: 处理大型文件时,要注意避免内存溢出。可以使用SXSSF...

    POI 最新源代码

    4. **事件模型(EventModel)**: 为了解决大文件处理问题,POI提供了一种基于事件的模型,称为SXSSF(Streaming Usermodel API)。这种模型可以在内存有限的情况下处理大量数据,通过只保持最近使用的行在内存中。 ...

Global site tag (gtag.js) - Google Analytics