java 读写有大量数据的 excel 的方法

本贴最后更新于 4143 天前,其中的信息可能已经时过境迁

场景:

     需要读取超过5万行的数据,然后经过业务处理后,再写入到excel中。

问题描述:

    当数据量大的时候,在读和写如果采用普通的poi给予的方法都会报heap OutOfMemoryError的问题。

解决方案:

    1、读数据解决方案

      

import java.io.InputStream;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.xssf.model.SharedStringsTable;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;

/**

  • XSSF and SAX (Event API)
    */
    public abstract class XxlsAbstract 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 int preCol = 0; //上一列列索引
    private int titleRow = 0; //标题行,一般情况下为 0
    private int rowsize = 0; //列数
    private String flag ="";//保存报表类型
    private List<Map<String,String>>listReportMap = null;
    //excel 记录行操作方法,以行索引和行元素列表为参数,对一行元素进行操作,元素为 String 类型
    // public abstract void optRows(int curRow, List<String> rowlist) throws SQLException ;

    //excel 记录行操作方法,以 sheet 索引,行索引和行元素列表为参数,对 sheet 的一行元素进行操作,元素为 String 类型
    public abstract void optRows(int sheetIndex,int curRow, List<String> rowlist,String flag,List<Map<String,String>>listReportMap) throws SQLException;

    //只遍历一个 sheet,其中 sheetId 为要遍历的 sheet 索引,从 1 开始,1-3
    public void processOneSheet(String filename,int sheetId,String flag,List<Map<String,String>> listReportMap) throws Exception {
    this.listReportMap = listReportMap;
    this.flag = flag;
    OPCPackage pkg = OPCPackage.open(filename);
    XSSFReader r = new XSSFReader(pkg);
    SharedStringsTable sst = r.getSharedStringsTable();

     XMLReader parser = fetchSheetParser(sst);  
    
     // rId2 found by processing the Workbook  
     // 根据 rId# 或 rSheet# 查找sheet  
     InputStream sheet2 = r.getSheet(&quot;rId&quot;+sheetId);  
     sheetIndex++;  
     InputSource sheetSource = new InputSource(sheet2);  
     parser.parse(sheetSource);  
     sheet2.close();  
    

    }

    /**

    • 遍历 excel 文件
      */
      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 (name.equals("c")) {
    // 如果下一个元素是 SST 的索引,则将 nextIsString 标记为 true
    String cellType = attributes.getValue("t");
    String rowStr = attributes.getValue("r");
    curCol = this.getRowIndex(rowStr);
    if (cellType != null && cellType.equals("s")) {
    nextIsString = true;
    } else {
    nextIsString = 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) {

         }  
     }  
    
     // v =&gt; 单元格的值,如果单元格是字符串则v标签的值为该字符串在SST中的索引  
     // 将单元格内容加入rowlist中,在这之前先去掉字符串前后的空白符  
     if (name.equals(&quot;v&quot;)) {  
         String value = lastContents.trim();  
         value = value.equals(&quot;&quot;)?&quot; &quot;:value;  
         int cols = curCol-preCol;  
         if (cols&gt;1){  
             for (int i = 0;i &lt; cols-1;i++){  
                 rowlist.add(preCol,&quot;&quot;);  
             }  
         }  
         preCol = curCol;  
         rowlist.add(curCol-1, value);  
     }else {  
         //如果标签名称为 row ,这说明已到行尾,调用 optRows() 方法  
         if (name.equals(&quot;row&quot;)) {  
             int tmpCols = rowlist.size();  
             if(curRow&gt;this.titleRow &amp;&amp; tmpCols&lt;this.rowsize){  
                 for (int i = 0;i &lt; this.rowsize-tmpCols;i++){  
                     rowlist.add(rowlist.size(), &quot;&quot;);  
                 }  
             }  
             try {  optRows(sheetIndex,curRow,rowlist,this.flag,this.listReportMap); } catch (SQLException e) {  
                 e.printStackTrace();  
             }  
             if(curRow==this.titleRow){  
                 this.rowsize = rowlist.size();  
             }  
             rowlist.clear();  
             curRow++;  
             curCol = 0;  
             preCol = 0;  
         }  
     }  
    

    }

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

    //得到列索引,每一列 c 元素的 r 属性构成为字母加数字的形式,字母组合为列索引,数字组合为行索引,
    //如 AB45,表示为第(A-A+1)*26+(B-A+1)*26 列,45 行
    public int getRowIndex(String rowStr){
    rowStr = rowStr.replaceAll("[^A-Z]", "");
    byte[] rowAbc = rowStr.getBytes();
    int len = rowAbc.length;
    float num = 0;
    for (int i=0;i<len;i++){
    num += (rowAbc[i]-'A'+1)*Math.pow(26,len-i-1 );
    }
    return (int) num;
    }

    public int getTitleRow() {
    return titleRow;
    }

    public void setTitleRow(int titleRow) {
    this.titleRow = titleRow;
    }
    }

使用方法

     这个方法是在读2007版本的时候,可以自己写一个类然后继承这个抽象类。重写optRows。

    在146行可以看到,这个抽象类,会调用子类你重写的方法,所以再子类中的方法可以直接拿到该数据。这种读的方式速度很快,5w行数据也就6,7秒就可以搞定

2、写大量数据解决方案

    可以将写出的数据批量写出,1w行数据一个sheet,或者分批写不同的excel。就我测试的数据来看。分批写不同的excel速度会比较快。

注:以上的读取代码来自于网络

  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3190 引用 • 8214 回帖 • 1 关注
  • POI
    23 引用 • 21 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...