poi sax 方式 导入excel海量数据

这几天做excel导入数据.刚开始使用jxl导入03版的,因为数据量超过65536,03版装不下,所以就用poi来支持07版的.
直接网上copy了个代码过去,搞了2条数据测试下成功了.
结果在导入正式数据的时候,才20M的excel文件就内存溢出了.网上搜了下,这情况很常见,需要使用sax方式来解析才可以.
但是在使用中,历经磨难,所以记录下来,以增强记忆.
还有好几个问题没解决,也一起记录下来,看哪位帮忙解决.
代码总共三段. 1.解析.2.抽象接口.3.业务类

1.解析类,继承DefaultHandler
[java] view plain copy print?在CODE上查看代码片派生到我的代码片
/**
* 抽象Excel2007读取器,excel2007的底层数据结构是xml文件,采用SAX的事件驱动的方法解析
* xml,需要继承DefaultHandler,在遇到文件内容时,事件会触发,这种做法可以大大降低
* 内存的耗费,特别使用于大数据量的文件。
*
*/
public class Excel2007Reader extends DefaultHandler {
//共享字符串表
private SharedStringsTable sst;
//上一次的内容
private String lastContents;
private boolean nextIsString;

private int sheetIndex = -1;  
private List<String> rowlist = new ArrayList<String>();  
//当前行  
private int curRow = 0;  
//当前列  
private int curCol = 0;  

// 当前遍历的Excel单元格列索引    
protected int thisColumnIndex = -1;  

private String defaultStr = "";    
private boolean listIsNull = true;    

protected int total = 0;  //总行数  
private int totalCol = 0;//总列数  

private String sheetName;  

private IRowReader rowReader;  
public void setRowReader(IRowReader rowReader){  
    this.rowReader = rowReader;  
}  


/** 
 * 根据sheetid 解析sheet 
 * @param stream 
 * @param sheetId 
 * @throws Exception 
 */  
public void processOneSheetByIndex(String filename,int sheetId) throws Exception {  
    OPCPackage pkg = OPCPackage.open(filename);  
    XSSFReader r = new XSSFReader(pkg);  
    SharedStringsTable sst = r.getSharedStringsTable();  
    XMLReader parser = fetchSheetParser(sst);  

    // 根据 rId# 查找sheet  
    InputStream sheet2 = r.getSheet("rId"+sheetId);  
    sheetIndex++;  
    InputSource sheetSource = new InputSource(sheet2);  
    parser.parse(sheetSource);  
    sheet2.close();  
}  

/** 
 * 根据sheet名称,解析sheet 
 * @param stream 
 * @param name 
 * @throws Exception 
 */  
public void process(InputStream stream) throws Exception {  
    OPCPackage pkg = OPCPackage.open(stream);  
    XSSFReader r = new XSSFReader(pkg);  
    SharedStringsTable sst = r.getSharedStringsTable();  
    XMLReader parser = fetchSheetParser(sst);  
    XSSFReader.SheetIterator iter = (XSSFReader.SheetIterator) r.getSheetsData();  
    while (iter.hasNext()) {  
        InputStream ist = iter.next();  
        sheetName = iter.getSheetName();  
        InputSource sheetSource = new InputSource(ist);  
        parser.parse(sheetSource);  
        ist.close();  
    }  
}  

public XMLReader fetchSheetParser(SharedStringsTable sst)  
        throws SAXException {  
    XMLReader parser = XMLReaderFactory .createXMLReader("org.apache.xerces.parsers.SAXParser");  
    this.sst = sst;  
    parser.setContentHandler(this);  
    return parser;  
}  

public void startElement(String uri, String localName, String name,  
        Attributes attributes) throws SAXException {  

    // c => 单元格  
    if ("c".equals(name)) {  
        // 如果下一个元素是 SST 的索引,则将nextIsString标记为true  
        String cellType = attributes.getValue("t");  
        if ("s".equals(cellType)) {  
            nextIsString = true;  
        } else {  
            nextIsString = false;  
        }  

        String r = attributes.getValue("r");    
        int firstDigit = -1;    
        for (int c = 0; c < r.length(); ++c) {    
            if (Character.isDigit(r.charAt(c))) {    
                firstDigit = c;    
                break;    
            }    
        }    
        thisColumnIndex = nameToColumn(r.substring(0, firstDigit)); ;  
    } else if (name.equals("row")) {    
        // 设置行号    
        if(listIsNull){    
            totalCol = getColumns(attributes.getValue("spans"));    
            listIsNull = false;    
        }    
    }else if (name.equals("dimension")){    
        //获得总计录数    
        String d = attributes.getValue("ref");    
        total = getNumber(d.substring(d.indexOf(":")+1,d.length()));    
    }    

    // 置空  
    lastContents = "";  
}  

public void endElement(String uri, String localName, String name)  
        throws SAXException {  
    // 根据SST的索引值的到单元格的真正要存储的字符串  
    // 这时characters()方法可能会被调用多次  
    if (nextIsString) {  
        try {  
            int idx = Integer.parseInt(lastContents);  
            lastContents = new XSSFRichTextString(sst.getEntryAt(idx))  
                    .toString();  
        } catch (Exception e) {  

// e.printStackTrace();
}
}
if (“v”.equals(name)) {
paddingNullCell();
String value = lastContents.trim();
value = value.equals(“”)?” “:value;

        rowlist.add(curCol, value);  
        curCol++;  
    }else {  
        //如果标签名称为 row ,这说明已到行尾,调用 optRows() 方法  
        if (name.equals("row")) {  
            //对最后的空列做填充  
            for(int i=rowlist.size();i<totalCol;i++){  
                rowlist.add(i,defaultStr);  
            }  

// optRows(sheetIndex,curRow,rowlist,total);
try {
rowReader.getRows(sheetIndex,curRow,rowlist,total,sheetName);
} catch (UIException e) {
throw new SAXException(e.getMessage());
}
rowlist.clear();
curRow++;
curCol = 0;
thisColumnIndex = 0;
}
}
}

private static int getNumber(String column) {    
    String c = column.toUpperCase().replaceAll("[A-Z]", "");    
    return Integer.parseInt(c);    
}    

public void characters(char[] ch, int start, int length)  
        throws SAXException {  
    //得到单元格内容的值  
    lastContents += new String(ch, start, length);  
}  


private static int getColumns(String spans) {    
    String number = spans.substring(spans.lastIndexOf(':') + 1,    
            spans.length());    
    return Integer.parseInt(number);    
}    

/** 
 * 空的单元个填充 
 */  
private void paddingNullCell() {  
    int index = curCol;  
    if(thisColumnIndex > index){  
        for(int i = index; i < thisColumnIndex; i++){  
            rowlist.add(curCol, "");  
            curCol++;  
        }  
    }  
}  
/**  
 * 从列名转换为列索引  
 * @param name  
 * @return  
 */   
private static int nameToColumn(String name) {    
    int column = -1;    
    for (int i = 0; i < name.length(); ++i) {    
        int c = name.charAt(i);    
        column = (column + 1) * 26 + c - 'A';    
    }    
    return column;    
}   

@SuppressWarnings("unused")  
private void optRows(int sheetIndex,int curRow, List<String> rowlist, int total2) {   
    for (int i = 0; i < rowlist.size(); i++) {    
        System.out.print("'" + rowlist.get(i) + "',");    
    }    
    System.out.println("");    
}    

    public static void main(String[] args) throws Exception {    
        long begintime,endtime;  
        Excel2007Reader howto = new Excel2007Reader();    
        howto.processOneSheetByIndex("E:/2012.xlsx",1);  
    }    

}

2.接口
[java] view plain copy print?在CODE上查看代码片派生到我的代码片
public interface IRowReader {
public void getRows(int sheetIndex,int curRow, List rowlist, int total, String sheetName) throws UIException;
}

3.业务类
[java] view plain copy print?在CODE上查看代码片派生到我的代码片
public class ContractTempManagerImpl implements IRowReader {

//判断是07版则执行07版解析  
public void importContract(UploadFile uploadFile) throws Exception{  
    String fileName = uploadFile.getFile().getOriginalFilename();  
    num=1;  
    long begintime,endtime;  
    //执行插入,1000条提交一次  
    begintime = new Date().getTime();  
    String type = fileName.substring(fileName.lastIndexOf('.')+1, fileName.length());  
    if(StringUtil.isNotBlank(type)&&type.equals("xlsx")){  
        Excel2007Reader rea = new Excel2007Reader();  
        rea.setRowReader(this);  
        rea.process(uploadFile.getFile().getInputStream());  
    }else{  
        //03版处理  
    }  
    endtime = new Date().getTime();  
    System.out.println("插入所需时间为 : " + (endtime - begintime)/1000 + "秒!");  

}  

/** 
 * 实现接口 
 * @param sheetIndex     
 * @param curRow    当前行 
 * @param rowlist   行结果 
 * @param total 总行数() 
 * @param sheetName sheet名称 
 * @throws UIException 
 */  
public void getRows(int sheetIndex, int curRow, List<String> rowlist,int total,String sheetName) throws UIException {  
    if(sheetName.equals("合同信息")){  
        this.saveContractTemp(sheetIndex,curRow,rowlist,total,sheetName);  
    }else if(sheetName.equals("合同对方")){  
        this.saveContractProviderTemp(sheetIndex,curRow,rowlist,total,sheetName);  
    }else{  
        throw new UIException("sheet名称有误,请核对!");  
    }  
}  

}

代码说明
1.解析里面的,大部分都是网上copy的,很多都不太懂.主要是要继承DefaultHandler, 和重写startElement,endElement.
然后在 endElement中调用业务类的操作.

这里说下我遇到的问题和解决方法及没有解决的:
1. 多个模版,每个模版多个sheet,解析类怎么公用.
解决: 在解析类中用了个接口的属性rowReader,endElement()调用rowReader.getRows(),然后业务类实现这个接口,就解决了多个模版复用代码的问题.
然后在getRows()中将sheet名称作为参数传递,则在同个业务类中根据sheet不同执行不同操作也解决.
实践中才更能理解面向接口编程的意义.

2.根据sheet名称解析指定sheet
解决: 网上没找到根据sheet名称解析的,都是根据id和全部解析.所以就用了全部解析,然后拿到sheet名称.

3.获取sheet的总行数.
解决:因为是用的批处理,业务类的list每2000条提交一次,最后一批可能不到2000,所以需要总行数用来判断是否最后一批,
这个很简单.代码中dimension类型,getNumber()方法.(这个方法没看太懂,不过无所谓了.)

4.空值的处理
解决:如果excel中某列为空,那么解析时候是读不到的,因为xml中根本就没有这列.(将excel后缀名修改为.zip,即可看xml文件)
网上copy了个方法paddingNullCell(). 应该是根据当前列号和上个列号做比较,来填充.

5.最后一列空值的处理
解决:上面说网上copy的方法解决了中间的空列,但是最后一列空的话却没有解决,所以我就自己写了个.
在startElement()中,第一行的时候,获得总列数(第一行是标题,以标题列数位置)
在endElement()中,行结束前,先看list的size是否是总列数,如果不是则补充

6.数字 3995.992, 读取到为 3995.9920000000002
未解决: 这个在编辑栏看到也是3995.992,并且设置很多小数位后,还是3995.9920000000000 ,没有出现2
在修改为zip查看xml文件中,xml显示为3995.9920000000002
很莫名其妙,设置格式为文本,并且双击然后让单元格前面出现文本标识那样,则xml中正常.
网上找了很多,也没找到解决办法. 例如截取,正则什么的, 都不适用.
最后只有用最笨的办法,手动一列一列的 分列为文本格式.大哭

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值