小石头
Excellence in any department can be attained only by the labor of a lifetime; it is not to be purchased at a lesser price.
posts - 91,comments - 22,trackbacks - 0

    前些天做了一个EXCEL数据下载的东西,发现当数据超过10万行之后,就会内存溢出(用的是本机TOMCAT测试,内存有限,没能调过),新做一种方式,来感觉一下,发现速度有点慢,其他还可以. 

一、问题描述

    该问题出现是因为在导出文件之后 用户下载的是 .csv 文件,如果用文本编辑器打开可以查看所有记录,但是如果用 excel 打开就出现一个 sheet 最多 6 万条的记录。因此就不可以使用保存为 csv 文件来实现 excel 文件的下载,需要使用新的方式去实现。

    如果使用 jxl 开发包在 web 后台去创建 Excel 文件,如果数据量比较大,则用户需要等待很长很长的时间才可以下载到,因为 jxl 的对于 excel 文件的操作都是对象级的 , 文件中每一个格子都是一个 cell 对象,需要后台去 new 。所以还需要考虑别的方式。

   

二、实现灵感

    Excel 文件打开之后选择另存为可以保存为 XML 类型文件,因此就考虑构造符合 Excel 可以打开的 XML 类型文件,并且对该 XML 文件进行最简单化处理,去除 Excel 文件中的每个 cell style 定义、 XML 文件头部的多余信息,最后整理出一个符合资讯平台所下载的 Excel 文件的格式 , 请看下面:

 

 

<!—XML 文件头部 --//>

<?xml version="1.0"?>

<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"

 xmlns:o="urn:schemas-microsoft-com:office:office"

 xmlns:x="urn:schemas-microsoft-com:office:excel"

 xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"

 xmlns:html="http://www.w3.org/TR/REC-html40">

 

<!—Excel 文件第一个 SHEET --//>

<Worksheet ss:Name="sheet0">

<Table>

<Row>

<Cell><Data ss:Type="String">aa0</Data></Cell>

<Cell><Data ss:Type="String">aa1</Data></Cell>

<Cell><Data ss:Type="String">aa2</Data></Cell>

</Row>

<Row>

<Cell><Data ss:Type="String">aa0</Data></Cell>

<Cell><Data ss:Type="String">aa1</Data></Cell>

<Cell><Data ss:Type="String">aa2</Data></Cell>

</Row>

</Table>

</Worksheet>

 

<!—Excel 文件第二个 SHEET --//>

<Worksheet ss:Name="sheet1">

<Table>

    <!— 一行数据 --//>

<Row>

<Cell><Data ss:Type="String">aa0</Data></Cell>

<Cell><Data ss:Type="String">aa1</Data></Cell>

<Cell><Data ss:Type="String">aa2</Data></Cell>

</Row>

<Row>

<Cell><Data ss:Type="String">aa0</Data></Cell>

<Cell><Data ss:Type="String">aa1</Data></Cell>

<Cell><Data ss:Type="String">aa2</Data></Cell>

</Row>

</Table>

</Worksheet>

 

</Workbook>

 

<!-- XML 文件结束 --//>

 

 

注释:

a <Worksheet ss:Name="sheet0">  引号内部的是该 sheet 的名称。

b <Data ss:Type="String">aa1</Data>  ss:Type 的值是用来定义该 cell 格数据的类型,例如可以为 Number, 表是该 cell 格数据是数字。

 

 

三、实现方式

    通过第一步的分析可以发现只要我们构建这样格式的 XML 数据,就可以通过 Excel 打开,并且可以实现分 sheet 的样式。但是数据下载到用户本地是 XML 类型的话,那是没什么意义的,就算打开方式选择使用 Excel 可以打开,因此如何将用户下载的文件类型改为 XLS 呢?这里就可以通过在下载的 servlet 中设置 response.setContentType("application/vnd.ms-excel") 来实现。
我实现了五个类来封闭XML字符串(CellData,TableCell,TableRow,WorkBook,WorkSheet),写了一个测试类

package com.hoten.util.xmlxls;

import java.util.List;
import java.util.ArrayList;

public class WorkBook {
 private final static String XML_HEARDER = "<?xml version=\"1.0\"?>";
 
 private List sheetList = new ArrayList(); //存放每行多个sheet的list
 
 /**
  * 取得workbook的xml文件的头部字符串
  * @return
  */
 private String getHeader(){  
  return XML_HEARDER +
    "<Workbook xmlns=\"urn:schemas-microsoft-com:office:spreadsheet\"" + Contants.SEP_N +
    " xmlns:o=\"urn:schemas-microsoft-com:office:office\"" + Contants.SEP_N +
    " xmlns:x=\"urn:schemas-microsoft-com:office:excel\"" +  Contants.SEP_N +
    " xmlns:ss=\"urn:schemas-microsoft-com:office:spreadsheet\"" + Contants.SEP_N +
    " xmlns:html=\"http://www.w3.org/TR/REC-html40\">" + Contants.SEP_N ;
 } 
 
 private String getFoot(){
  return "</Workbook>";
 }
 
 public String toString(){
  StringBuffer strBuff = new StringBuffer();
  
  strBuff.append(getHeader());
  
  int len = sheetList.size();
  for(int i=0;i<len;i++){
   WorkSheet sheet = (WorkSheet)sheetList.remove(0);
   strBuff.append(sheet.toString());
   sheet = null;
  }  
  sheetList.clear();
  
  strBuff.append(getFoot());
  
  return strBuff.toString();
 }
 
 public void addSheet(WorkSheet sheet){
  sheetList.add(sheet);
 }
 
 public void removeSheet(int i){
  sheetList.remove(i);
 }  
}


package com.hoten.util.xmlxls;

import java.util.List;
import java.util.ArrayList;

public class WorkSheet {
 
 private String name = ""; //该sheet的name
 
 private List rowList = new ArrayList(); //存放每行多个row的list
 
 public String toString(){
  StringBuffer strBuff = new StringBuffer();
  
  strBuff.append("<Worksheet ss:Name=\"" + name + "\">").append(Contants.SEP_N);
  strBuff.append("<Table>").append(Contants.SEP_N);
  
  int len = rowList.size();
  for(int i=0;i<len;i++){
   TableRow row = (TableRow)rowList.remove(0);
   strBuff.append(row.toString());
   row = null;
  }  
  rowList.clear();
  
  strBuff.append("</Table>").append(Contants.SEP_N);
  strBuff.append("</Worksheet>").append(Contants.SEP_N);
  
  return strBuff.toString();
 }
 
 public void addRow(TableRow row){
  rowList.add(row);
 }
 
 public void removeRow(int i){
  rowList.remove(i);
 }

 public String getName() {
  return name;
 }

 public void setName(String name) {
  this.name = name;
 } 
}


package com.hoten.util.xmlxls;

import java.util.List;
import java.util.ArrayList;

public class TableRow {

 private List cellList = new ArrayList(); //存放每行多个cell的list
 
 
 public String toString(){
  StringBuffer strBuff = new StringBuffer();
  
  strBuff.append("<Row>").append(Contants.SEP_N);

  //循环显示每行的cell
  int len = cellList.size();
  for(int i=0;i<len;i++){
   TableCell cell = (TableCell)cellList.remove(0);
   strBuff.append(cell.toString()).append(Contants.SEP_N);
   cell = null;   
  }
  cellList.clear();
  
  strBuff.append("</Row>").append(Contants.SEP_N);
  
  return strBuff.toString();
 } 
 
 public void addCell(TableCell cell){
  cellList.add(cell);
 }
 
 public void removeCell(int i){
  cellList.remove(i);
 }
 
}


package com.hoten.util.xmlxls;

public class TableCell {
 private String index = ""; //cell在每行显示的索引位置,可以不填
 
 private CellData data = new CellData(); //cell的数据对象

 public CellData getData() {
  return data;
 }

 public void setData(CellData data) {
  this.data = data;
 }

 public String getIndex() {
  return index;
 }

 public void setIndex(String index) {
  this.index = index;
 }
 
 
 public String toString(){
  return "<Cell>" + data.toString() + "</Cell>";
 }
 
}


package com.hoten.util.xmlxls;

public class CellData {
 private String type = "String"; //cell数据类型
 private String value = ""; //cell数据
 
 public String getType() {
  return type;
 }
 public void setType(String type) {
  this.type = type;
 }
 public String getValue() {
  return value;
 }
 public void setValue(String value) {
  this.value = value;
 }
 
 
 public String toString(){
  return "<Data ss:Type=\"" + type + "\">" + value + "</Data>";
 }
}



package com.hoten.util.xmlxls;

public class Contants {
 public final static String SEP_N = "\n";
 /**
  * XML中常量定义
  */
 public final static String SS_NAME = "ss:Name";
 public final static String SS_INDEX = "ss:Index";
 public final static String SS_TYPE = "ss:Type";

}



测试的方法:
PrintWriter out = response.getWriter();
         // 设置响应头和下载保存的文件名
         response.setContentType("application/vnd.ms-excel");
         response.setHeader("Content-Disposition","attachment; filename=\""+Chinese.toPage(fileNametemp)+"\"");
        
         String rows_temp = request.getParameter("rows");//页面传过来的每个SHEET可以存放的条数
   if (rows_temp == null){
    rows_temp = "20000";
   }
   int count_rows = Integer.parseInt(rows_temp);//一个SHEET有多行
        
         WorkBook book = new WorkBook();
         Vector v_Rs = RsToVector.ResultSetToVector(rs);// 把RS的值转换成VECTOR,这个可以看我上一版本,上面有详细的写法
         
         if (v_Rs != null) {
    int a = v_Rs.size();    
    int sheet_count = a / count_rows;// 30000行一个sheet。
    
    if (sheet_count >0){//大于0,则需要多个SHEET
     for (int i = 0;i<sheet_count;i++){
      WorkSheet sheet = new WorkSheet();//创建一个新的SHEET
      sheet.setName("sheet" + i);//设置SHEET的名称
           
      for (int b = i* count_rows ;b<(i+1) * count_rows ;b++){       
       //temp_v.add(v_Rs.get(b));
       Vector temp_v = new Vector();
       temp_v =(Vector) v_Rs.get(b);       
       //取出一个对象,把对象的值放到EXCEL里
       TableRow row = new TableRow();//设置行 
       for(int m=0;m<numColumns;m++){
              TableCell cell = new TableCell();
              CellData data = new CellData();
              data.setValue((String)temp_v.get(m));
              cell.setData(data);              
              row.addCell(cell);
             }             
             sheet.addRow(row);
      } 
      book.addSheet(sheet);
     }
     //还有剩余的数据
     if (sheet_count * count_rows < a){
      WorkSheet sheet = new WorkSheet();//创建一个新的SHEET
      sheet.setName("sheet" + sheet_count);//设置SHEET的名称
       
      for (int c = sheet_count* count_rows ;c<a ;c++){       
       Vector temp_vv = new Vector();
       temp_vv =(Vector) v_Rs.get(c);
       //取出一个对象,把对象的值放到EXCEL里
       TableRow row = new TableRow();//设置行
       for(int m=0;m<numColumns;m++){
              TableCell cell = new TableCell();
              CellData data = new CellData();
              data.setValue((String)temp_vv.get(m));
              cell.setData(data);              
              row.addCell(cell);
             }             
             sheet.addRow(row);       
      }
      book.addSheet(sheet);       
     }
    }else{
     
     
      WorkSheet sheet = new WorkSheet();//创建一个新的SHEET
      sheet.setName("sheet1");//设置SHEET的名称
           
      for (int bb=0  ;bb<a ;bb++){       
       //temp_v.add(v_Rs.get(b));
       Vector temp_v = new Vector();
       temp_v =(Vector) v_Rs.get(bb);       
       //取出一个对象,把对象的值放到EXCEL里
       TableRow row = new TableRow();//设置行 
       for(int m=0;m<numColumns;m++){
              TableCell cell = new TableCell();
              CellData data = new CellData();
              data.setValue((String)temp_v.get(m));
              cell.setData(data);              
              row.addCell(cell);
             }             
             sheet.addRow(row);
      } 
      book.addSheet(sheet);
    }
        
    out.print(book.toString());
   }
    以上拼XML的时候重复代码比较多,可以写一个公用的方法,,我为了把XML描述的详细一点,把这些都封装成了对象,但在拼字符串的时候,对象就会太多,以后如果改版的话,可以把它尽量封装少一点对象,这样速度可能会快一点,内存可能会少用一点.

 

转 : http://www.blogjava.net/wujiaqian/archive/2006/12/11/86970.html

posted on 2007-01-08 15:38 小石头 阅读(1778) 评论(1)  编辑  收藏 所属分类: 转载区我的java学习

FeedBack:
# re: 通过构造XML数据流下载成Excel文件[转]
2012-08-02 12:56 | 随便你叫
这真心要感谢微软大大们的努力以及开放出这个东西!  回复  更多评论
  

只有注册用户登录后才能发表评论。


网站导航: