使用POI或JXLS导出大数据量(百万级)Excel报表常常面临两个问题:
1. 服务器内存溢出;
2. 一次从数据库查询出这么大数据,查询缓慢。
当然也可以分页查询出数据,分别生成多个Excel打包下载,但这种生成还是很缓慢。
大数据量导入请参考:Java实现大批量数据导入导出(100W以上) -(一)导入
那么如何解决呢?
我们可以借助XML格式利用模板替换,分页查询出数据从磁盘写入XML,最终会以Excel多sheet形式生成。亲测2400万行数据,生成Excel文件4.5G,总耗时1.5分钟。
我利用StringTemplate模板解析技术对XML模板进行填充。当然也可以使用FreeMarker, Velocity等Java模板技术实现。
首先引入StringTemplate所需Jar包:
使用技术为 stringTemplate
pom.xml:
1 <dependency> 2 <groupId>antlr</groupId> 3 <artifactId>antlr</artifactId> 4 <version>2.7.7</version> 5 </dependency> 6 7 <dependency> 8 <groupId>org.antlr</groupId> 9 <artifactId>stringtemplate</artifactId> 10 <version>3.2.1</version> 11 </dependency>
首先准备导出Excel模板,然后打开-》另存为-》选择格式为XML,然后用文本打开XML,提取XML头模板(head.st可通用),数据体模板(boday.st):
head.st可通用:
1 <?xml version="1.0"?> 2 <?mso-application progid="Excel.Sheet"?> 3 <Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet" 4 xmlns:o="urn:schemas-microsoft-com:office:office" 5 xmlns:x="urn:schemas-microsoft-com:office:excel" 6 xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet" 7 xmlns:html="http://www.w3.org/TR/REC-html40"> 8 <DocumentProperties xmlns="urn:schemas-microsoft-com:office:office"> 9 <Created>1996-12-17T01:32:42Z</Created> 10 <LastSaved>2013-08-02T09:21:24Z</LastSaved> 11 <Version>11.9999</Version> 12 </DocumentProperties> 13 <OfficeDocumentSettings xmlns="urn:schemas-microsoft-com:office:office"> 14 <RemovePersonalInformation/> 15 </OfficeDocumentSettings> 16 <ExcelWorkbook xmlns="urn:schemas-microsoft-com:office:excel"> 17 <WindowHeight>4530</WindowHeight> 18 <WindowWidth>8505</WindowWidth> 19 <WindowTopX>480</WindowTopX> 20 <WindowTopY>120</WindowTopY> 21 <AcceptLabelsInFormulas/> 22 <ProtectStructure>False</ProtectStructure> 23 <ProtectWindows>False</ProtectWindows> 24 </ExcelWorkbook> 25 <Styles> 26 <Style ss:ID="Default" ss:Name="Normal"> 27 <Alignment ss:Vertical="Bottom"/> 28 <Borders/> 29 <Font ss:FontName="宋体" x:CharSet="134" ss:Size="12"/> 30 <Interior/> 31 <NumberFormat/> 32 <Protection/> 33 </Style> 34 </Styles>
boday.st:
1 $worksheet:{ 2 <Worksheet ss:Name="$it.sheet$"> 3 <Table ss:ExpandedColumnCount="$it.columnNum$" ss:ExpandedRowCount="$it.rowNum$" x:FullColumns="1" 4 x:FullRows="1" ss:DefaultColumnWidth="54" ss:DefaultRowHeight="14.25"> 5 $it.rows:{ 6 <Row> 7 <Cell><Data ss:Type="String">$it.name1$</Data></Cell> 8 <Cell><Data ss:Type="String">$it.name2$</Data></Cell> 9 <Cell><Data ss:Type="String">$it.name3$</Data></Cell> 10 </Row> 11 }$ 12 </Table> 13 </Worksheet> 14 }$
生成大数据量Excel类:
ExcelGenerator:
1 package test.exportexcel; 2 3 import org.antlr.stringtemplate.StringTemplate; 4 import org.antlr.stringtemplate.StringTemplateGroup; 5 import test.exportexcel.bean.Row; 6 import test.exportexcel.bean.Worksheet; 7 8 import java.io.*; 9 import java.util.ArrayList; 10 import java.util.List; 11 import java.util.Random; 12 13 /** 14 * 类功能描述:generator big data Excel 15 * 16 * @author WangXueXing create at 19-4-13 下午10:23 17 * @version 1.0.0 18 */ 19 public class ExcelGenerator { 20 public static void main(String[] args) throws FileNotFoundException{ 21 ExcelGenerator template = new ExcelGenerator(); 22 template.output2(); 23 } 24 25 /** 26 * 生成数据量大的时候,该方法会出现内存溢出 27 * @throws FileNotFoundException 28 */ 29 public void output1() throws FileNotFoundException{ 30 StringTemplateGroup stGroup = new StringTemplateGroup("stringTemplate"); 31 StringTemplate st4 = stGroup.getInstanceOf("test/exportexcel/template/test"); 32 List<Worksheet> worksheets = new ArrayList<>(); 33 34 File file = new File("/home/barry/data/output.xls"); 35 PrintWriter writer = new PrintWriter(new BufferedOutputStream(new FileOutputStream(file))); 36 37 for(int i=0;i<30;i++){ 38 Worksheet worksheet = new Worksheet(); 39 worksheet.setSheet("第"+(i+1)+"页"); 40 List<Row> rows = new ArrayList<>(); 41 for(int j=0;j<6000;j++){ 42 Row row = new Row(); 43 row.setName1("zhangzehao"); 44 row.setName2(""+j); 45 row.setName3(i+" "+j); 46 rows.add(row); 47 } 48 worksheet.setRows(rows); 49 worksheets.add(worksheet); 50 } 51 52 st4.setAttribute("worksheets", worksheets); 53 writer.write(st4.toString()); 54 writer.flush(); 55 writer.close(); 56 System.out.println("生成excel完成"); 57 } 58 59 /** 60 * 该方法不管生成多大的数据量,都不会出现内存溢出,只是时间的长短 61 * 经测试,生成2400万数据,2分钟内,4.5G大的文件,打开大文件就看内存是否足够大了 62 * 数据量小的时候,推荐用JXLS的模板技术生成excel文件,谁用谁知道,大数据量可以结合该方法使用 63 * @throws FileNotFoundException 64 */ 65 public void output2() throws FileNotFoundException{ 66 long startTimne = System.currentTimeMillis(); 67 StringTemplateGroup stGroup = new StringTemplateGroup("stringTemplate"); 68 69 //写入excel文件头部信息 70 StringTemplate head = stGroup.getInstanceOf("test/exportexcel/template/head"); 71 File file = new File("/home/barry/data/output.xls"); 72 PrintWriter writer = new PrintWriter(new BufferedOutputStream(new FileOutputStream(file))); 73 writer.print(head.toString()); 74 writer.flush(); 75 76 int sheets = 400; 77 //excel单表最大行数是65535 78 int maxRowNum = 60000; 79 80 //写入excel文件数据信息 81 for(int i=0;i<sheets;i++){ 82 StringTemplate body = stGroup.getInstanceOf("test/exportexcel/template/body"); 83 Worksheet worksheet = new Worksheet(); 84 worksheet.setSheet(" "+(i+1)+" "); 85 worksheet.setColumnNum(3); 86 worksheet.setRowNum(maxRowNum); 87 List<Row> rows = new ArrayList<>(); 88 for(int j=0;j<maxRowNum;j++){ 89 Row row = new Row(); 90 row.setName1(""+new Random().nextInt(100000)); 91 row.setName2(""+j); 92 row.setName3(i+""+j); 93 rows.add(row); 94 } 95 worksheet.setRows(rows); 96 body.setAttribute("worksheet", worksheet); 97 writer.print(body.toString()); 98 writer.flush(); 99 rows.clear(); 100 rows = null; 101 worksheet = null; 102 body = null; 103 Runtime.getRuntime().gc(); 104 System.out.println("正在生成excel文件的 sheet"+(i+1)); 105 } 106 107 //写入excel文件尾部 108 writer.print("</Workbook>"); 109 writer.flush(); 110 writer.close(); 111 System.out.println("生成excel文件完成"); 112 long endTime = System.currentTimeMillis(); 113 System.out.println("用时="+((endTime-startTimne)/1000)+"秒"); 114 } 115 }
定义JavaBean:
WorkSheet.java:
1 package test.exportexcel.bean; 2 3 import java.util.List; 4 5 /** 6 * 类功能描述:Excel sheet Bean 7 * 8 * @author WangXueXing create at 19-4-13 下午10:21 9 * @version 1.0.0 10 */ 11 public class Worksheet { 12 private String sheet; 13 private int columnNum; 14 private int rowNum; 15 private List<Row> rows; 16 17 public String getSheet() { 18 return sheet; 19 } 20 public void setSheet(String sheet) { 21 this.sheet = sheet; 22 } 23 24 public List<Row> getRows() { 25 return rows; 26 } 27 public void setRows(List<Row> rows) { 28 this.rows = rows; 29 } 30 31 public int getColumnNum() { 32 return columnNum; 33 } 34 public void setColumnNum(int columnNum) { 35 this.columnNum = columnNum; 36 } 37 38 public int getRowNum() { 39 return rowNum; 40 } 41 public void setRowNum(int rowNum) { 42 this.rowNum = rowNum; 43 } 44 }
Row.java:
1 package test.exportexcel.bean; 2 3 /** 4 * 类功能描述:Excel row bean 5 * 6 * @author WangXueXing create at 19-4-13 下午10:22 7 * @version 1.0.0 8 */ 9 public class Row { 10 private String name1; 11 private String name2; 12 private String name3; 13 14 public String getName1() { 15 return name1; 16 } 17 public void setName1(String name1) { 18 this.name1 = name1; 19 } 20 21 public String getName2() { 22 return name2; 23 } 24 public void setName2(String name2) { 25 this.name2 = name2; 26 } 27 28 public String getName3() { 29 return name3; 30 } 31 public void setName3(String name3) { 32 this.name3 = name3; 33 } 34 }
另附实现源码: exportexcel.zip
此外,大数据量并并且Excel列较多时,会出现内存溢出。可参考如下文章解决。