使用POI操作大量Excel表格数据

由于需要分析一个以xls格式保存的数据,所以就在网上搜了一下java的操作excel文件的库,发现了Apache的POI,“Apache POI是Apache軟件基金會的開放源碼函式庫,POI提供API給Java程式對Microsoft Office格式檔案讀和寫的功能。 .NET的開發人員則可以利用NPOI (POI for .NET) 來存取 POI 的功能。”--维基百科
POI可以操作各种office文档,excel当然只是其中一种啦。
下载地址:
[url]http://poi.apache.org/download.html[/url]
官方网站的quick guide地址:
[url]http://poi.apache.org/spreadsheet/quick-guide.html[/url]
最简单的方式就是使用POI的user model,也就是quick guide上介绍的方法,这个大家一看就懂,也不赘述了。但是这个方法有个局限,就是使用user model读xls文件时要实例化一个HSSFWorkbook对象,这个对象会载入你要读取的excel文件。那当你的数据非常大的时候,据说当xls文件超过两三MB的时候,就会发生JVM的out of memory异常。像我要处理的xls文件有20多mb,这个方法就不适用了,我还尝试提高JVM的内存上限,结果无济于事。
没有捷径可走于是我又重新上网查,对付这种大文件,一次性读取肯定是不现实的,POI还提供了event user model进行线性读写操作。这个event user model就没有前面的user model好懂了(从名字上多了5个字母就可以看出)。官网上的代码非常晦涩,后来又上网搜到了许多类似的代码,多半是在一堆莫名其妙的代码上小添加了几行注释。这里就说一下我的理解。
event model说明POI在处理excel时采用的事件处理机制,类似于SAX处理XML数据的方式。一个xls文件是一串字节流,在POI看来,可以认为是一串Record流。所以POI可以为Record规定一个事件处理函数,POI线性地读取Record,每当读到一个Record时,就相应调用它的事件处理函数。而用户需要做的就是自定义事件处理函数。POI就好比一个传送带,它把各种Record按顺序送到你的面前来,而你呢,就是根据不同的Record做不同的操作即可。
public class tryEventModel {
private static final String TEST_FILE = "files/workbook.xls";

public static void main(String args[]) {
File file = new File(TEST_FILE);
FileInputStream fis;
try {
fis = new FileInputStream(file);
POIFSFileSystem pfs;
pfs = new POIFSFileSystem(fis);
InputStream is;
is = pfs.createDocumentInputStream("Workbook");

HSSFRequest request = new HSSFRequest();
// 这儿为所有类型的Record都注册了监听器,如果需求明确的话,可以用addListener方法,并指定所需的Record类型
request.addListenerForAllRecords(new MyListener());

HSSFEventFactory factory = new HSSFEventFactory();
factory.processEvents(request, is);

fis.close();
// TODO Auto-generated catch block
is.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

监听器代码
public class MyListener implements HSSFListener {  
//记录下来字符串表
private SSTRecord strRec;
public void processRecord(Record record) {
switch (record.getSid()){
case BOFRecord.sid:
BOFRecord br = (BOFRecord)record;
switch (br.getType()) {
case BOFRecord.TYPE_WORKBOOK: //顺序进入新的Workbook
System.out.println("新workbook");
break;
case BOFRecord.TYPE_WORKSHEET://顺序进入新的Worksheet,因为Event API不会把Excel文件里的所有数据结构都关联起来,所以这儿一定要记录现在进入第几个sheet了。
System.out.println("新worksheet");
break;
}
break;
case BoundSheetRecord.sid: //记录sheet,这儿会把所有的sheet都顺序打印出来,如果有多个sheet的话,可以顺序记入到一个List里
BoundSheetRecord bsr = (BoundSheetRecord) record;
System.out.println("sheetName:"+bsr.getSheetname());
break;
case SSTRecord.sid: //记录字符串表
strRec = (SSTRecord)record;
System.out.println("字符串表");
break;
case RowRecord.sid: //打印行,这个用处不大
RowRecord rr = (RowRecord) record;
System.out.println("行:"+rr.getRowNumber()+"。 開始列:"+rr.getFirstCol()+", 終了列:"+rr.getLastCol());
break;
case NumberRecord.sid: //发现数字类型的cell,因为数字和日期都是用这个格式,所以下面一定要判断是不是日期格式,另外默认的数字也会被视为日期格式,所以如果是数字的话,一定要明确指定格式!!!!!!!
NumberRecord nr = (NumberRecord)record;
if(HSSFDateUtil.isInternalDateFormat(nr.getXFIndex())){
System.out.println("日付:"+(new SimpleDateFormat("yyyy-MM-dd")).format(HSSFDateUtil.getJavaDate(nr.getValue()))
+", 行:"+nr.getRow()+ ", 列:"+nr.getColumn()+"。("+nr.getXFIndex()+")");
}else{
System.out.println("数字:"+nr.getValue()+", 行:"+nr.getRow()+ ", 列:"+nr.getColumn()+"。("+nr.getXFIndex()+")");
}
break;
case LabelSSTRecord.sid: //发现字符串类型,这儿要取字符串的值的话,跟据其index去字符串表里读取
LabelSSTRecord lsr = (LabelSSTRecord)record;
System.out.println("文字列:"+strRec.getString(lsr.getSSTIndex())+", 行:"+lsr.getRow()+", 列:"+lsr.getColumn());
break;
case BoolErrRecord.sid: //boolean or error
BoolErrRecord ber = (BoolErrRecord)record;
if(ber.isBoolean()){
System.out.println("Boolean:"+ber.getBooleanValue()+", 行:"+ber.getRow()+", 列:"+ber.getColumn());
}
if(ber.isError()){
System.out.println("Error:"+ber.getErrorValue()+", 行:"+ber.getRow()+", 列:"+ber.getColumn());
}
break;
case BlankRecord.sid:
BlankRecord br1 = (BlankRecord)record;
System.out.println("空。 行:"+br1.getRow()+", 列:"+br1.getColumn());
break;
case FormulaRecord.sid: //数式
FormulaRecord fr = (FormulaRecord)record;
break;
}
}

}

官网上说,event user model需要你了解基本的excel文件的格式。其实这也很简单,你只需做一个简单的xls文件,然后跑一遍程序就知道POI输出这些各种Record的顺序了。在这里要说一下SSTRecord strRec,这个在Listener中是一个私有变量,它就像一个集合类,表格里的所有字符串都会存在这个变量里,通过index取出,所以需要它设为私有变量。
POI会严格按照顺序读取Record,遍历每一个sheet中每一行的每一列内容。剩下的如何操作就由用户自己设置了。
你可以设置一个currentRow属性来判断当前读取的位置,把这一行的数据都存在一个数组中。当使用getRow得出的当前行和currentRow不一致时,说明换行了,你就可以处理这一行的数据,然后清空数组重新开始读下一行,这就是我采用的逐行读取&操作的例子。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值