POI實現大數據EXCLE導入導出,解決內存溢出問題


  使用POI能夠導出大數據保證內存不溢出的一個重要原因是SXSSFWorkbook生成的EXCEL為2007版本,修改EXCEL2007文件后綴為ZIP打開可以看到,每一個Sheet都是一個xml文件,單元格格式和單元格坐標均用標簽表示。直接使用SXSSFWorkbook來到導出EXCEL本身就是POI為了大數據量導出而量身定制的,所以導出可以直接使用SXSSFWorkbook方式。

  為了保險起見可以采用多Sheet的方式保證內存不溢出。需要注意的是Sheet名稱不能重復;下載的時候需要定義好返回頭。

response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");

  導出EXCEL較為簡單,創建Workbook對象和Sheet對象往里塞值就行了。但是導入讀取EXCEL的時候SXSSFWorkbook沒有讀取文件流的方法,只能使用XSSFWorkbook來讀取,幾千條數據可能就內存溢出了。

  這時候就要使用OPCPackage

public static OPCPackage open(java.io.InputStream in)
                       throws InvalidFormatException,
                              java.io.IOException

Open a package. Note - uses quite a bit more memory than open(String), which doesn't need to hold the whole zip file in memory, and can take advantage of native methods

Parameters:
    in - The InputStream to read the package from
Returns:
    A PackageBase object
Throws:
    InvalidFormatException
    java.io.IOException

  POI給出的API表示使用OPCPackage不需要將文件完全讀取到內存中。

  調用方法

File file = uploadFile.getFile();
InputStream is = new FileInputStream(file);
excelReader.readInputStream(is);
excelReader.process();

  ExcelReader.java

/**
 * 抽象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;
    //日期標志
    private boolean dateFlag;
    //數字標志
    private boolean numberFlag;
    
    private boolean isTElement;
    
    private IRowReader rowReader;
    
    public void setRowReader(IRowReader rowReader){
        this.rowReader = rowReader;
    }
    
    /**只遍歷一個電子表格,其中sheetId為要遍歷的sheet索引,從1開始,1-3
     * @param filename
     * @param sheetId
     * @throws Exception
     */
    public void processOneSheet(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# 或 rSheet# 查找sheet
        InputStream sheet2 = r.getSheet("rId"+sheetId);
        sheetIndex++;
        InputSource sheetSource = new InputSource(sheet2);
        parser.parse(sheetSource);
        sheet2.close();
    }

    /**
     * 遍歷工作簿中所有的電子表格
     * @param filename
     * @throws Exception
     */
    public void process(String filename) throws Exception {
        OPCPackage pkg = OPCPackage.open(filename);
        XSSFReader r = new XSSFReader(pkg);
        SharedStringsTable sst = r.getSharedStringsTable();
        XMLReader parser = fetchSheetParser(sst);
        Iterator<InputStream> sheets = r.getSheetsData();
        while (sheets.hasNext()) {
            curRow = 0;
            sheetIndex++;
            InputStream sheet = sheets.next();
            InputSource sheetSource = new InputSource(sheet);
            parser.parse(sheetSource);
            sheet.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 cellDateType = attributes.getValue("s");
            if ("1".equals(cellDateType)){
                dateFlag = true;
            } else {
                dateFlag = false;
            }
            String cellNumberType = attributes.getValue("s");
            if("2".equals(cellNumberType)){
                numberFlag = true;
            } else {
                numberFlag = false;
            }
            
        }
        //當元素為t時
        if("t".equals(name)){
            isTElement = true;
        } else {
            isTElement = false;
        }
        
        // 置空
        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) {

            }
        } 
        //t元素也包含字符串
        if(isTElement){
            String value = lastContents.trim();
            rowlist.add(curCol, value);
            curCol++;
            isTElement = false;
            // v => 單元格的值,如果單元格是字符串則v標簽的值為該字符串在SST中的索引
            // 將單元格內容加入rowlist中,在這之前先去掉字符串前后的空白符
        } else if ("v".equals(name)) {
            String value = lastContents.trim();
            value = value.equals("")?" ":value;
            //日期格式處理
            if(dateFlag){
                 Date date = HSSFDateUtil.getJavaDate(Double.valueOf(value));
                 SimpleDateFormat dateFormat = new SimpleDateFormat(
                 "dd/MM/yyyy");
                 value = dateFormat.format(date);
            } 
            //數字類型處理
            if(numberFlag){
                BigDecimal bd = new BigDecimal(value);
                value = bd.setScale(3,BigDecimal.ROUND_UP).toString();
            }
            rowlist.add(curCol, value);
            curCol++;
        }else {
            //如果標簽名稱為 row ,這說明已到行尾,調用 optRows() 方法
            if (name.equals("row")) {
                rowReader.getRows(sheetIndex,curRow,rowlist);
                rowlist.clear();
                curRow++;
                curCol = 0;
            }
        }
        
    }

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM