大数据量excel2007导入(大于10M)-java

    技术2025-05-18  51

    最近的项目中碰到这样一个问题,用户需要导入大数据量的数据,要导入的数据文件大于7M,用户在系统使用的高峰期进行导入,结果导致应用服务器的cpu负载过高,其他用户无法使用系统,系统崩溃的情况。

     

    我们之前没有考虑到这么大数据量的导入,我们使用的是poi3.6版本,用的方式是把用户要导入的文件读成流,然后一次性放到poi的WorkBook中,经过测试,当文件的大小大于5M左右,tomcat服务器的内存就会溢出,从而导致服务器崩溃。

     

    为了解决这个问题,我在网上查了一些资料,解决的方式大概如下(此种方式只适用于excel2007):

     

    1. 采用的读取方式是事件模型eventusermodel,因为excel2007采用的存储方式实际上是xml形式,excel2007可以用系统自带的解压缩软件解压之后可以看到数据都是存储成xml格式的数据,所以这里采用sax的方式对excel文件进行解析。

     

    2. 我在网上下载了2个类文件

     

    抽象类:XxlsAbstract ,作用:遍历excel文件,提供行级操作方法 optRows

    继承类:XxlsBig,作用:将数据转出到数据库临时表 temp_table

    另外,还需要一个属性文件 db.properties,把数据库的链接写到属性文件中。

     

    3. 另外我自己写了一个多线程的程序,把临时表 temp_table 的数据再写到真正需要导入的数据库表中

     

    4. 写完数据把临时表删除。

     

    程序经过测试,50M左右的文件在本地进行导入都比较稳定。

     

    但是有几个问题不知道怎么办?

     

    1. excel2007文件转换成xml格式的文件之后,可以看到,如果某一行的某个单元格没有内容,xml中就不显示这个单元格,也就是说此种方式不能处理空的单元格。

    解决方案:我自己是这么处理的,在导入之前,先把左右的空单元格统一替换成1,然后在java后台在把1替换回来,这样做肯定很影响导入的性能,但是暂时只想到这么个办法。

     

    2. 之前的导入传到service层的是一个文件流,现在XxlsBig类中接收的参数仅仅是一个文件路径,所以如果我的程序发布到应用服务器上,肯定是拿不到要导入的文件的。

     

    总之,还有一些细节的问题弄不明白,需要csdn的兄弟姐妹们给予指导,谢谢啦~~

     

    2011年2月22日补充:

    在javaeye上看到了原创作者的补充,解决了问题1,

    代码如下:

     

     package com.gaosheng.util.xls;     import java.io.InputStream;   import java.sql.SQLException;   import java.util.ArrayList;   import java.util.Iterator;   import java.util.List;     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;    //列数              //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) throws SQLException;              //只遍历一个sheet,其中sheetId为要遍历的sheet索引,从1开始,1-3       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);             // rId2 found by processing the Workbook           // 根据 rId# 或 rSheet# 查找sheet           InputStream sheet2 = r.getSheet("rId"+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 => 单元格的值,如果单元格是字符串则v标签的值为该字符串在SST中的索引           // 将单元格内容加入rowlist中,在这之前先去掉字符串前后的空白符           if (name.equals("v")) {               String value = lastContents.trim();               value = value.equals("")?" ":value;               int cols = curCol-preCol;               if (cols>1){                   for (int i = 0;i < cols-1;i++){                       rowlist.add(preCol,"");                   }               }               preCol = curCol;               rowlist.add(curCol-1, value);           }else {               //如果标签名称为 row ,这说明已到行尾,调用 optRows() 方法               if (name.equals("row")) {                   int tmpCols = rowlist.size();                   if(curRow>this.titleRow && tmpCols<this.rowsize){                       for (int i = 0;i < this.rowsize-tmpCols;i++){                           rowlist.add(rowlist.size(), "");                       }                   }                   try {                       optRows(sheetIndex,curRow,rowlist);                   } 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;       }   } 

    问题2有如下解决方案:

    我们项目中用到的导入采用了夜间模式,就是普通用户进行导入操作的时候,系统仅仅是把文件上传到文件服务器,并且在数据库表把该文件的路径,上传作者写到特定的数据库表。

    我们采用了spring的定时任务,在夜间2点开始扫描指定的数据库表,如果数据库表中存在记录,即有需要进行导入的数据文件,那么这个时候才启动,进行导入,这个时候由于文件是在文件服务器,所以只要指定一个路径,就可以进行文件的读取了。

    最新回复(0)